SQL query that reports N or more consecutive absents from attendance table

走远了吗. 提交于 2019-12-24 08:35:15

问题


I have a table that looks like this:

studentID | subjectID | attendanceStatus | classDate  | classTime | lecturerID |
12345678    1234        1                  2012-06-05   15:30:00
87654321
12345678    1234        0                  2012-06-08   02:30:00

I want a query that reports if a student has been absent for 3 or more consecutive classes. based on studentID and a specific subject between 2 specific dates as well. Each class can have a different time. The schema for that table is:

PK(`studentID`, `classDate`, `classTime`, `subjectID, `lecturerID`)

Attendance Status: 1 = Present, 0 = Absent

Edit: Worded question so that it is more accurate and really describes what was my intention.


回答1:


I wasn't able to create an SQL query for this. So instead, I tried a PHP solution:

  1. Select all rows from table, ordered by student, subject and date
  2. Create a running counter for absents, initialized to 0
  3. Iterate over each record:
    • If student and/or subject is different from previous row
      • Reset the counter to 0 (present) or 1 (absent)
    • Else, that is when student and subject are same
      • Set the counter to 0 (present) or plus 1 (absent)

I then realized that this logic can easily be implemented using MySQL variables, so:

SET @studentID = 0;
SET @subjectID = 0;
SET @absentRun = 0;

SELECT *,
CASE
    WHEN (@studentID  = studentID) AND (@subjectID  = subjectID) THEN @absentRun := IF(attendanceStatus = 1, 0, @absentRun + 1)
    WHEN (@studentID := studentID) AND (@subjectID := subjectID) THEN @absentRun := IF(attendanceStatus = 1, 0, 1)
END AS absentRun
FROM table4
ORDER BY studentID, subjectID, classDate

You can probably nest this query inside another query that selects records where absentRun >= 3.

SQL Fiddle




回答2:


This query works for intended result:

SELECT DISTINCT first_day.studentID 
FROM student_visits first_day
LEFT JOIN student_visits second_day
    ON first_day.studentID = second_day.studentID
    AND DATE(second_day.classDate) - INTERVAL 1 DAY = date(first_day.classDate)
LEFT JOIN student_visits third_day
    ON first_day.studentID = third_day.studentID
    AND DATE(third_day.classDate) - INTERVAL 2 DAY = date(first_day.classDate)
WHERE first_day.attendanceStatus = 0 AND second_day.attendanceStatus = 0 AND third_day.attendanceStatus = 0

It's joining table 'student_visits' (let's name your original table so) to itself step by step on consecutive 3 dates for each student and finally checks the absence on these days. Distinct makes sure that result willn't contain duplicate results for more than 3 consecutive days of absence.

This query doesn't consider absence on specific subject - just consectuive absence for each student for 3 or more days. To consider subject simply add .subjectID in each ON clause:

    ON first_day.subjectID = second_day.subjectID

P.S.: not sure that it's the fastest way (at least it's not the only).




回答3:


Unfortunately, mysql does not support windows functions. This would be much easier with row_number() or better yet cumulative sums (as supported in Oracle).

I will describe the solution. Imagine that you have two additional columns in your table:

  • ClassSeqNum -- a sequence starting at 1 and incrementing by 1 for each class date.
  • AbsentSeqNum -- a sequence starting a 1 each time a student misses a class and then increments by 1 on each subsequent absence.

The key observation is that the difference between these two values is constant for consecutive absences. Because you are using mysql, you might consider adding these columns to the table. They are big challenging to add in the query, which is why this answer is so long.

Given the key observation, the answer to your question is provided by the following query:

select studentid, subjectid, absenceid, count(*) as cnt
from (select a.*,  (ClassSeqNum - AbsentSeqNum) as absenceid
      from Attendance a
     ) a
group by studentid, subjectid, absenceid
having count(*) > 2

(Okay, this gives every sequence of absences for a student for each subject, but I think you can figure out how to whittle this down just to a list of students.)

How do you assign the sequence numbers? In mysql, you need to do a self join. So, the following adds the ClassSeqNum:

select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
     Attendance a1
     on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
        a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId

And the following adds the absence sequence number:

select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
     Attendance a1
     on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
        a.ClassDate >= a1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId

So the final query looks like:

with cs as (
    select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
    from Attendance a join
         Attendance a1
         on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
            a.ClassDate >= s1.classDate
    group by a.StudentId, a.SubjectId
),
    a as (
    select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
    from Attendance a join
         Attendance a1
         on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
            a.ClassDate >= s1.classDate
    where AttendanceStatus = 0
    group by a.StudentId, a.SubjectId
)
select studentid, subjectid, absenceid, count(*) as cnt
from (select cs.studentid, cs.subjectid,
             (cs.ClassSeqNum - a.AbsentSeqNum) as absenceid 
      from cs join
           a
           on cs.studentid = a.studentid and cs.subjectid = as.subjectid
     ) a
group by studentid, subjectid, absenceid
having count(*) > 2


来源:https://stackoverflow.com/questions/10562495/sql-query-that-reports-n-or-more-consecutive-absents-from-attendance-table

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!