It appears the OP is conflating how the data should be stored in the database with how the data will be displayed and/or entered when taking classroom attendance.
These are not necessarily the same thing. Display should not drive storage.
The UI could display the data the way the OP shows above in a classroom attendance form and have a middle layer that maps those values to and from more normalized tables in the database.
More work for the middle layer but more extensible and configurable when
(1) the attendance day is longer or shorter
(2) students are added or removed from the class
An entry in an CLASS_ATTENDANCE table will have all the data that changes on a daily basis with foreign keys for the class, student, and status.
(0) CLASS_ATTENDANCE ID
(1) CLASS ID
(2) STUDENT ID
(3) DATE
(4) STATUS CODE
(5) and perhaps TEACHER ID of the person who took attendance
An entry in a CLASS table will have
(0) CLASS ID
(1) COURSE ID
(2) PERIOD ID
(3) and perhaps TEACHER ID of the person who normally teaches the class
An entry in a PERIOD table will have
(0) PERIOD ID
(1) START TIME (for example 08:00)
(2) END TIME (for example 08:55)
Periods usually do not change during a single semester
There would like be a CLASS_STUDENT table that tracks which students are assigned to which class and drives filling in the CLASS_ATTENDANCE table daily.
(0) CLASS_STUDENT ID
(1) CLASS ID
(2) STUDENT ID
This just one way to normalize the data. And there is still some redundancy between the CLASS_STUDENT table and the CLASS_ATTENDANCE table.
Something like this would give you the total across all three of those days:
SELECT COUNT(attendance.AttendanceID) as 'Total Attendance'
FROM student
INNER JOIN attendance ON attendance.StudentID = student.StudentID
AND attendance.Date IN ('01/04/2015', '02/04/2015', '05/04/2015')
WHERE student.WorkshopID = '101'
If you wanted the total separately for each of those dates, use this:
SELECT attendance.Date, COUNT(attendance.AttendanceID) as 'Total Attendance'
FROM student
INNER JOIN attendance ON attendance.StudentID = student.StudentID
AND attendance.Date IN ('01/04/2015', '02/04/2015', '05/04/2015')
WHERE student.WorkshopID = '101'
GROUP BY attendance.Date
Try this for overall attendance. Keep in mind I didn't have time to setup the schema, so this is untested:
SELECT wsdata.WorkshopID
, wsdata.TotalAttendanceDaysPossible
, adata.TotalAttendanceDaysActual
, ((adata.TotalAttendanceDaysActual / wsdata.TotalAttendanceDaysPossible) * 100) AS OverallAttendance
FROM (
SELECT ws1.WorkshopID
, ws1.NumberOfWeeks AS TotalDays
, COUNT(s1.StudentID) AS TotalStudents
, ws1.NumberOfWeeks * COUNT(s1.StudentID) AS TotalAttendanceDaysPossible
FROM workshop ws1
INNER JOIN student s1 on ws1.WorkshopID = s1.WorkshopID
GROUP BY ws1.WorkshopID
, ws1.NumberOfWeeks
) wsdata
INNER JOIN (
SELECT s2.WorkshopID
, COUNT(a.AttendanceID) AS TotalAttendanceDaysActual
FROM attendance a
INNER JOIN student s2 ON a.StudentID = s2.StudentID
GROUP BY s2.WorkshopID
) adata ON wsdata.WorkshopID = adata.WorkshopID
WHERE wsdata.WorkshopID = '101'
Best Answer
I would go for the first option, mostly because I believe that less columns are better.
But also:
First of all, the first option would only require an insert when a student is absent, while the second would, first, have an insert for each student at each month, and then for each student that is absent, it would require to update the row each time the student is absent.
The second option would however give a good matrix directly in the database which days a student is absent, but that would be easily achieved through PHP.
Also, having one row per day a student is absent, allows for better statistics when it comes to reporting.
For instance, check how many students that were absent during one month:
compared to (here I assume that d1..d31 is an integer with either 1 or 0)
Or, if you have a
student
table as well (which I assume you will have) and you want to see which students were not absent:Compared to:
Finally, since you wanted to store the reason for the student to be absent, it means for option 2 you would either, have one more row for each reason or have one more column for each day which stores the reason, since otherwise you could only store one reason per month, unless you store the reason as a concatenated string in the column which would require a separator and thus limit the characters you put in as reason.
However, in the end it is more of a personal preference, since with correctly created indexes, I doubt that you would have to worry about the performance, but depending on the amount of absent students, option 2 would be using more storage than option 1 (number of columns * rows * size of the data types)