所以您的餐桌出勤率存储了哪个学生的信息
(由 student_id 标识)在哪个班级(由 group_id 标识)
在哪一天(由日期确定) 在哪一课(由日期确定)
HOUR_X)?
出勤表
首先,让我们看看您的表架构(没有类列,只有 3 个句点):
CREATE TABLE IF NOT EXISTS attendance_daily (
student_id int, day date, period1 text, period2 text, period3 text);
还有一些样本数据:
INSERT INTO attendance_daily
VALUES (1, '2017-01-01', 'present', 'present', 'present'),
(2, '2017-01-01', 'present', 'absent', 'excused');
对于两个学生,一个三个小时都在场,另一个在第一个小时出现,第二个小时缺席,第三个小时被医生原谅。
下面是我们如何将这 2 行变成 6 行,但具有共同的属性(id、day、period):
SELECT t.id, t.day, t.period FROM
(SELECT student_id, day, period1 FROM attendance_daily
UNION ALL SELECT student_id, day, period2 FROM attendance_daily
UNION ALL SELECT student_id, day, period3 FROM attendance_daily
) AS t (id, day, period)
ORDER BY id, day, period;
返回
id | day | period
----+------------+---------
1 | 2017-01-01 | present
1 | 2017-01-01 | present
1 | 2017-01-01 | present
2 | 2017-01-01 | absent
2 | 2017-01-01 | excused
2 | 2017-01-01 | present
UNION ALL 是必需的,否则id = 1 将只有 1 行,而不是 3!
并计算存在状态(statuses):
SELECT t.id, t.day, t.presence, COUNT(*) AS count FROM
(SELECT student_id, day, period1 FROM attendance_daily
UNION ALL SELECT student_id, day, period2 FROM attendance_daily
UNION ALL SELECT student_id, day, period3 FROM attendance_daily
) AS t (id, day, presence)
GROUP BY id, day, presence ORDER BY id, day, presence ;
返回
id | day | presence | count
----+------------+----------+-------
1 | 2017-01-01 | present | 3
2 | 2017-01-01 | absent | 1
2 | 2017-01-01 | excused | 1
2 | 2017-01-01 | present | 1
现在您可以例如说,每天缺席 3 次以上?那是错过并得到报告或其他东西。
您现在可以在此表上选择并找出哪位学生在哪一天出席、缺席和缺席了多少次。
现在,如果您出于演示的原因想要将这些行组成一个字符串,那么您可以按照此StackOverflow Answer for SQL-Server。
根据您的 SQL-Server 版本,您也可以只使用 string_agg 函数(如果可用)。
不同的出勤表
如果您将出勤率存储在不同的表架构中,那么使用它会简单得多:
CREATE TABLE attendances (student_id int NOT NULL,
day date NOT NULL,
class int NOT NULL,
period int NOT NULL, <-- HOUR_X: hour_1 <=> period = 1
presence int NOT NULL);
-- student_id | day | classes | period AKA hour | presence_status
INSERT INTO attendances VALUES (1, '2017-01-01', 10, 1, 2),
(1, '2017-01-01', 10, 2, 1),
(1, '2017-01-01', 10, 3, 1),
(1, '2017-01-01', 10, 4, 1);
这代表了这样一个事实,即学生 1 在 2017 年 1 月 1 日第 10 课的第一个小时睡过头(第一行缺席 = 2),并且在一天的剩余时间里都在场(状态 = 1)(这意味着最后 3 行中的周期 2、3 和 4)。
然后SELECT 和计数(简单得多):
SELECT student_id, day, class, presence, COUNT( presence ) AS count_presence
FROM attendances
GROUP BY student_id, day, class, presence
ORDER BY 2, 3, 1, 4, 5;
student_id | day | class | presence | count_presence
------------+------------+-------+----------+----------------
1 | 2017-01-01 | 10 | 1 | 3
1 | 2017-01-01 | 10 | 2 | 1
对于UPDATE,例如将presence 状态设置为hour (period) = 1,例如因为学生迟到了5 分钟(不是他的错):
UPDATE attendances
SET presence = 1
WHERE student_id = 1 AND day = '2017-01-01' AND class = 10;
得到
student_id | day | class | presence | count_presence
------------+------------+-------+----------+----------------
1 | 2017-01-01 | 10 | 1 | 4
attendances (student_id, day, class, period, presence) 的建议 PRIMARY UNIQUE KEY 将是 (student_id, day, presence)。
这使每个学生都可以根据需要参加尽可能多的课程和天数。但是对于每个(student, day, presence) 组合,他只能在一个class 中,而恰好是一个presence_status。
这样,学生们每天也可以在不同的班级。
外键参考
这是关于 FK 引用的一些内容。
您只想存储实际存在的学生的出勤率。
换句话说,如果您的attendances_daily 表中有student_id 的行,那么它肯定应该作为学生存在于students 表中。
您可以像这样在创建表时实现这一点(首先创建学生表,否则没有学生ID指向):
CREATE TABLE IF NOT EXISTS attendance_daily (
student_id int REFERENCES students (student_id), <-- FOREIGN KEY
day date, period1 text, period2 text, period3 text);
或分开
CREATE TABLE IF NOT EXISTS attendance_daily (
student_id int,
day date, period1 text, period2 text, period3 text,
FOREIGN KEY students_id REFERENCES students (student_id) <-- FOREIGN KEY
);
您也可以稍后使用ALTER TABLE 添加外键引用。
NULLS
一般来说,你应该努力避免NULLs。 NULL 可能具有模棱两可的含义。这可能意味着数据目前不可用,不存在也永远不会存在,或者作为数字属性中的数字零。关于你的表attendances_daily,presence_status = NULL 是什么意思?和缺席一样吗?原谅?你只是没有数据吗?那你为什么还要存储一行呢?因为您的行存储信息,这些信息可以独立于presence_status?那么你的标准化可能没有看起来那么好?
有时 NULL 也可以。但请仔细考虑是否需要 NULL。它也可以使查询结果令人惊讶。