0

I have been trying to get total hours attended by students. I want to get it for every day by using case statement with no luck.In my table, each hour can have one of 3 statues (attend, absent, absent with excuse) and can have null as well

Attendance_Daily Table format Hour_X can be 1 or 2 or 3 for (attend, absent, absent with excuse)

StudentID| GroupID| date | Hour_1 | Hour_2 | Hour_3 | Hour_4 | Hour_5 | Hour_6 | Hour_7 | Hour_8

Groups (classes) Table format

Group_ID | Group_Name | Course_ID | Attendance_Type | Attempts_for_attendance | Instructor_ID | Start_Date | End_Date | Start_Time| End_Time 

classes Data example

Group_ID | Group_Name | Course_ID | Attendance_Type | Attempts_for_taking_attendance_perday |Instructor_ID | Start_Date | End_Date | Start_Time| End_Time |
---------+------------+-----------+-----------------+---------------------------------------+--------------+------------+----------+-----------+----------+-----
    1    |sql class 1 | 1         |       1         |                   1                   |    1         | 01-01-2017 |30-01-2017 |8:00 AM   | 12:00 PM
    1    |sql class 2 | 1         |       2         |                   3                   |    2         | 01-01-2017 |30-01-2017 |8:00 AM   | 12:00 PM

Final result total column needed example and cases: the result can be :

number of hours attended

or A as string code when hours in statues 2

or E as string code when hours in statues 3

StudentID | Date       | Total Result | Note only not in result
----------+------------+--------------+--------------------------------------
    1     | 30-05-2017 | 8            |hours attended statues 1
    2     | 30-05-2017 | 3            |hours attended statues 1
    3     | 30-05-2017 | A            |absent none of the hours attended
    4     | 30-05-2017 | E            |absent with excuse>>  none of the hours attended >> statues 3
    5     | 30-05-2017 | 3             |need attended total 3  , when  3 hours attended  and absent 3 hours 

I hope you can help with this

Abdulaziz
  • 3
  • 4
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/143627/discussion-on-question-by-abdulaziz-sql-server-case-multiple-column-statues). – Bhargav Rao May 07 '17 at 18:41

2 Answers2

0

So your table attendance stores the information which student (identified by student_id) was in which class (identified by group_id) at what day (identified by date) in which lesson (identified by HOUR_X)?

ATTENDANCE TABLE

First, let's take your table schema (without the class column and only 3 periods):

CREATE TABLE IF NOT EXISTS attendance_daily (
student_id int, day date, period1 text, period2 text, period3 text);

And a bit of sample data:

INSERT INTO attendance_daily 
VALUES (1, '2017-01-01', 'present', 'present', 'present'),
       (2, '2017-01-01', 'present', 'absent', 'excused');

For two students, the one present all three hours, the other present the first hour, missing the second, and excused by the doctor the third.

So here is how we can make these 2 rows into 6 rows, but with common attributes (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;  

which returns

 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

The UNION ALL is necessary, because otherwise there would be only 1 row with id = 1, instead of 3!

And to count the presence stati (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 ;  

which returns

 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

Now you can for example go and say, more than 3 times absent per day? That's a miss and getting a report or something. You can now select on this table and fish out which student was present, absent and absent excused how many times at which day.

Now, if you want to form these rows into a string for presentation reasons, then you can follow this StackOverflow Answer for SQL-Server. Depending on your SQL-Server version, you can also just use the string_agg function, if it's available.

DIFFERENT ATTENDANCES TABLE

If you store your attendances in a different table schema, then working with it is much simpler:

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);

Which represents the fact, that student 1 overslept (ABSENT = 2 in the first row) the first hour of class 10 on day 2017-01-01, and was present (status = 1) for the rest of the day (which means periods 2, 3 and 4 in the last 3 rows).

And to SELECT and count (so much simpler):

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

And to UPDATE, for example set the presence status at hour (period) = 1, for example because the student just arrived 5 minutes late (not his fault):

UPDATE attendances  
SET presence = 1  
WHERE student_id = 1 AND day = '2017-01-01' AND class = 10;  

to get

 student_id |    day     | class | presence | count_presence 
------------+------------+-------+----------+----------------
          1 | 2017-01-01 |    10 |        1 |              4

The proposed PRIMARY UNIQUE KEY for attendances (student_id, day, class, period, presence) would be (student_id, day, presence). This allows every student to participate in as many classes and days as he wants. But for every (student, day, presence) combination, he can only be in one class with precisely one presence_status. This way, the students can also be in different classes per day.

FOREIGN KEY REFERENCES Here is something about FK references. You only want to store attendances for students who actually exist. In other words, if there is a row with student_id in your attendances_daily table, then it most certainly should exist as a student in the students table.

You can achieve this on creation of the table like so (create the students table first, otherwise there is no students_id to point to):

CREATE TABLE IF NOT EXISTS attendance_daily (  
student_id int REFERENCES students (student_id),  <-- FOREIGN KEY  
day date, period1 text, period2 text, period3 text);  

Or seperately

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  
);

You can also go for ALTER TABLE to add a foreign key reference later on.

NULLS In general you should strive to avoid NULLs. NULLs may have an ambiguous meaning. It can mean that data is not available at the moment, does not and will never exist, or as the number zero in a numeric attribute. With respect to your table attendances_daily, what is the meaning of a presence_status = NULL? Is it the same as absent? excused? Do you just not have the data? Then why would you store a row anyway? Because your row stores information, that can exist independently of the presence_status? Then your normalization might not be as good as it seems?

Sometimes NULLs are also okay. But think carefully about whether you want NULLs. It can make query results surprising, too.

Community
  • 1
  • 1
flutter
  • 694
  • 3
  • 8
  • thank you so much , btw my table " Attendance_Daily" is only to store the Attendance for the student in that class, but it is not used to store which student belongs to which class or student info as already i have that, note that some classes or groups taking attendance once a day for "daily attendance" type and other classes have 4 hours and others 2 hours per day , so based on the classes attendance type we store the needed hours as configured in classes table but currently how i can calculate the total hours per the current table :( – Abdulaziz May 07 '17 at 03:32
  • Ok, so attendance_daily (group_id) is actually something referring to the length of the class. I will fix that later on. table class_duration (class_id int, length int). attendance_daily (class_id something here). I would recommend a general classes table to store all available classes, then a classes_schedule table which stores, which class is taking place at which hours at which day or something like that. For now i will assume all classes to have the same length; can fix later. – flutter May 07 '17 at 03:44
  • no it is not referring to the length of the class it is referring to class x in branch x in time and date and instructor x ..etc – Abdulaziz May 07 '17 at 03:47
  • I will post this right away: When you have the time (maybe tomorrow, next week or something), then please read up on database normal forms. Here is a good starter [Wikipedia](https://en.wikipedia.org/wiki/Database_normalization). You should always achieve at least third normal form 3NF. Then you will have a LOT less problems later on. You have a repeating group of hour_X columns, which (very often) put first normal form 1NF into jeopardy. This is the reason your query is hard to write! – flutter May 07 '17 at 03:47
  • btw this is for training classes (diploma, microsoft courses, english different type of classes ) not school classes – Abdulaziz May 07 '17 at 03:49
  • thank you for the link i will look into it of course , i think my tables are normalized and have master and details except for the hours part in the attendance but everything else i hope is ok – Abdulaziz May 07 '17 at 03:53
  • AFAIR, if a table is not in 1NF, then it cannot be in any higher normal form, because a higher normal form (2NF for example) requires the next lower normal form to be present (1NF here). How would you write a query, which asks if a student was ABSENT in exactly one hour? With 8 OR clauses? And accompanying hour1 = ABSENT AND hour2 = PRESENT and ... ??? You cannot GROUP BY hours, because you have several columns of the same type storing the same data. You should seriously consider refactoring it like i proposed above (attendance table). Then you can group by and other stuff easily, too. – flutter May 07 '17 at 04:06
  • If your other tables have primary keys and ids and unique constraints and are monothematic (single topic), then that is perfect. But your attendance_daily does not look the part, IMHO. it's your call anyway, so no worries (also: we are all learning SQL and DB stuff. I did some years ago :)) Meanwhile on the attendance_daily table ... – flutter May 07 '17 at 04:09
  • absolutely and i learned so much from this website, the main reason for the question is to learn something new , thank you so much – Abdulaziz May 07 '17 at 04:24
0

Please try the following...

SELECT StudentID,
       [date],
       CASE
           WHEN Group_Type = 'daily' AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'A : Absent for the day'
           WHEN Group_Type = 'hourly' AND
                Group_Frequency = 1 AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'A : Absent with the hour not attended'
           WHEN Group_Type = 'hourly' AND
                Group_Frequency > 1 AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'A : Absent with none of the hours attended'
           WHEN Group_Type = 'daily' AND
                CountPresent = 0 AND
                CountAbsent = 1 AND
                CountExcused = 0 THEN
               'A : Absent for the day, with 1 statue'
           WHEN Group_Type = 'hourly' AND
                Group_Frequency = 1 AND
                CountPresent = 0 AND
                CountAbsent = 1 AND
                CountExcused = 0 THEN
               'A : Absent with the hour not attended, with 1 statue'
           WHEN Group_Type = 'hourly' AND
                Group_Frequency > 1 AND
                CountPresent = 0 AND
                CountAbsent = Group_Frequency AND
                CountExcused = 0 THEN
               'A : Absent with none of the hours attended, with ' +
                   CountAbsent +
                   IIF( CountAbsent = 1, ' statue', ' statues' )
           WHEN Group_Type = 'daily' AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = 1 THEN
               'E : Excused for the day, with 1 statue' )
           WHEN Group_Type = 'hourly' AND
                Group_Frequency = 1 AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = 1 THEN
               'E : Excused for the hour, with 1 statue'
           WHEN Group_Type = 'hourly' AND
                Group_Frequency > 1 AND
                CountPresent = 0 AND
                CountAbsent = 0 AND
                CountExcused = Group_Frequency THEN
               'E : Excused for the ' +
                   CountExcused +
                   ' hours, with ` +
                   CountExcused +
                   ' statues'
           WHEN Group_Type = 'daily' AND
                CountPresent = 1 AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'Present for the day, with 1 statue'                   
           WHEN Group_Type = 'hourly' AND
                Group_Frequency = 1 AND
                CountPresent = 1 AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'Present for the hour, with 1 statue'                   
           WHEN Group_Type = 'hourly' AND
                Group_Frequency > 1 AND
                CountPresent = Group_Frequency AND
                CountAbsent = 0 AND
                CountExcused = 0 THEN
               'Present for the ' +
                   CountPresent +
                   ' hours, with ' +
                   CountPresent +
                   ' statues'
           WHEN Group_Type = 'hourly' AND
                ( CountPresent + CountAbsent ) <= Group_Frequency
                CountPresent > 0 AND
                CountAbsent > 0 AND
                CountExcused = 0 THEN
               'Present for ' +
                   CountPresent +
                   IIF( CountPresent = 1, ' hour', ' hours' ) +
                   ', Absent for ' +
                   IIF( CountAbsent = 1, ' hour', ' hours' ) +
                   ', with ' +
                   ( CountPresent + CountAbsent ) +
                   ' statues'
           WHEN Group_Type = 'hourly' AND
                ( CountPresent + CountExcused ) <= Group_Frequency
                CountPresent > 0 AND
                CountAbsent = 0 AND
                CountExcused > 0 THEN
               'Present for ' +
                   CountPresent +
                   IIF( CountPresent = 1, ' hour', ' hours' ) +
                   ', Excused for ' +
                   IIF( CountExcused = 1, ' hour', ' hours' ) +
                   ', with ' +
                   ( CountPresent + CountExcused ) +
                   ' statues'
           WHEN Group_Type = 'hourly' AND
                ( CountAbsent + CountExcused ) <= Group_Frequency
                CountPresent = 0 AND
                CountAbsent > 0 AND
                CountExcused > 0 THEN
               'Absent for ' +
                   CountAbsent +
                   IIF( CountAbsent = 1, ' hour', ' hours' ) +
                   ', Excused for ' +
                   IIF( CountExcused = 1, ' hour', ' hours' ) +
                   ', with ' +
                   ( CountAbsent + CountExcused ) +
                   ' statues'
           WHEN Group_Type = 'hourly' AND
                ( CountPresent + CountAbsent + CountExcused ) <= Group_Frequency
                CountPresent > 0 AND
                CountAbsent > 0 AND
                CountExcused > 0 THEN
               'Present for ' +
                   CountPresent +
                   IIF( CountPresent = 1, ' hour', ' hours' ) +
                   'Absent for ' +
                   CountAbsent +
                   IIF( CountAbsent = 1, ' hour', ' hours' ) +
                   ', Excused for ' +
                   IIF( CountExcused = 1, ' hour', ' hours' ) +
                   ', with ' +
                   ( CountPresent + CountAbsent + CountExcused ) +
                   ' statues'
           ELSE
               'Unexpected combination (GT = ' +
               Group_Type +
               ', GF = ' +
               Group_Frequency +
               ', CP = ' +
               CountPresent +
               ', CA = ' +
               CountAbsent +
               ', CE = ' +
               CountExcused
           END AS [Total Result]
FROM ( SELECT StudentID AS StudentID,
              [date] AS [date],
              IIF( Hour_1 = 1, 1, 0 ) +
                  IIF( Hour_2 = 1, 1, 0 ) +
                  IIF( Hour_3 = 1, 1, 0 ) +
                  IIF( Hour_4 = 1, 1, 0 ) +
                  IIF( Hour_5 = 1, 1, 0 ) +
                  IIF( Hour_6 = 1, 1, 0 ) +
                  IIF( Hour_7 = 1, 1, 0 ) +
                  IIF( Hour_8 = 1, 1, 0 ) AS CountPresent,
              IIF( Hour_1 = 2, 1, 0 ) +
                  IIF( Hour_2 = 2, 1, 0 ) +
                  IIF( Hour_3 = 2, 1, 0 ) +
                  IIF( Hour_4 = 2, 1, 0 ) +
                  IIF( Hour_5 = 2, 1, 0 ) +
                  IIF( Hour_6 = 2, 1, 0 ) +
                  IIF( Hour_7 = 2, 1, 0 ) +
                  IIF( Hour_8 = 2, 1, 0 ) AS CountAbsent,
              IIF( Hour_1 = 3, 1, 0 ) +
                  IIF( Hour_2 = 3, 1, 0 ) +
                  IIF( Hour_3 = 3, 1, 0 ) +
                  IIF( Hour_4 = 3, 1, 0 ) +
                  IIF( Hour_5 = 3, 1, 0 ) +
                  IIF( Hour_6 = 3, 1, 0 ) +
                  IIF( Hour_7 = 3, 1, 0 ) +
                  IIF( Hour_8 = 3, 1, 0 ) AS CountExcused,
       FROM tblAttendances
    ) AS countsFinder
JOIN ( SELECT StudentID,
              Group_Type,
              Group_Frequency
       FROM tblAttendences
       JOIN tblClass ON tblAttendances.GroupID = tblClass.GroupID
       GROUP BY StudentID,
                Group_Type,
                Group_Frequency
     ) AS StudentGroupDetails ON countsFinder.StudentID = StudentGroupDetails.StudentID
ORDER BY StudentID,
         [date];

This statement starts by using the IIF() function to compare each value of Hour_n for each record from tblAttendances to 1 (the value representing Present) and return 1 for where the values equal and returning 0 for where they do not. Once all of the values of Hour_n have been tested the values returned are added together, giving us a count of how many values of Hour_n equalled 1.

The same process is repeated for where each value of Hour_n equals 2 (for Absent) and for where each value of Hour_n equals 3. The resultant counts, along with the values of [date] and StudentID for that record, are added to the dataset to be returned by the subquery.

Once this process has been performed for each record by the subquery, the resulting dataset is joined to the dataset returned by a subquery that lists each StudentID and its corresponding values of Group_Type and Group_Frequency.

This gives us a joined dataset that contains each value of StudentID and their corresponding values of Group_Type and Group_Frequency along with each corresponding value of [date], CountPresent, CountAbsent and CountExcused.

Each record's values are then used to SELECT the StudentID and [date] values and a message based on the combination of the other values.

If you have any questions or comments, then please feel free to post a Comment accordingly.

toonice
  • 2,211
  • 1
  • 13
  • 20
  • this genius thank u, but the result is not correct in some cases i got the total hours attended increased by one can u plz check that – Abdulaziz May 07 '17 at 11:00
  • this is perfect and lead me to the answer i was looking for and solved the main issue, and the other answer is good as well thank you all so much – Abdulaziz May 07 '17 at 19:00