Skip to content
Advertisement

SQLServer limit results to 1 by two criteria

My company offers different courses. A course usually lasts five days and is usually taught by one teacher. But, there are exceptions where a course is split up beteween two teachers. Currently I have a SQL-statement which selects (or “limits”) the results down to the teacher for the first day of the course:

SELECT c.course_id,
       c.description,
       t.firstname AS teacher_firstname,
       t.lastname AS teacher_lastname,
       cd.day AS first_day,
       cd2.day AS last_day
FROM (SELECT *,
             ROW_NUMBER() OVER (PARTITION BY course_id ORDER BY day) AS rn
      FROM Course_days) cd
     JOIN (SELECT *,
                  ROW_NUMBER() OVER (PARTITION BY course_id ORDER BY day DESC) AS rn2
           FROM Course_days) cd2 ON cd.course_id = cd2.course_id
     JOIN Courses c ON cd.course_id = c.course_id
     JOIN Teachers t ON t.teacher_id = cd.teacher_id
WHERE rn = 1
  AND rn2 = 1;

Which returns something like this:

course_id description teacher_firstname teacher_lastname first_day last_day
101 HTML/CSS/JS basics February 2021 John Doe 2021-02-01 2021-02-05
102 C# February 2021 Jane Doe 2021-02-01 2021-02-05

As you can see in the DB structure below the course 101 has two teachers (bold entry).

Is there a possibility to get a result like this?

course_id description teacher_firstname teacher_lastname second_teacher_firstname second_teacher_lastname first_day last_day
101 HTML/CSS/JS basics February 2021 John Doe Mr. X 2021-02-01 2021-02-05
102 C# February 2021 Jane Doe NULL NULL 2021-02-01 2021-02-05

I have already spent hours searching for a possibile way to do that but didn’t really find anything.

DB Structure:

Courses:

course_id description
101 HTML/CSS/JS basics February 2021
102 C# basics February 2021
103 Java basics February 2021
104 HTML/CSS/JS basics March 2021
105 C# basics March 2021
106 Java basics March 2021

Teachers:

teacher_id firstname Lastname
1 John Doe
2 Jane Doe
3 Ex Ample
4 Mr. X

Course_Days:

course_day_id course_id (foreign key) teacher_id (foreign key) day course_day_number
548 101 1 2021-02-01 1
549 101 1 2021-02-02 2
550 101 1 2021-02-03 3
551 101 1 2021-02-04 4
552 101 4 2021-02-05 5
553 102 2 2021-02-01 1
554 102 2 2021-02-02 2
555 102 2 2021-02-03 3
556 102 2 2021-02-04 4
557 102 2 2021-02-05 5

Advertisement

Answer

Those teachers are tricky, because you (presumably) want to preserve the original order.

The approach taken here is generalizable to more teachers and keeps the names in order. For each teacher, it determines the first day that teacher teaches the course. It then uses that to define “teacher1” and “teacher2” using conditional aggregation:

SELECT c.course_id, c.description,
       MAX(CASE WHEN teacher_seqnum = 1 THEN t.firstname END) AS teacher1_firstname,
       MAX(CASE WHEN teacher_seqnum = 1 THEN t.lastname END) AS teacher1_lastname,
       MAX(CASE WHEN teacher_seqnum = 2 THEN t.firstname END) AS teacher2_firstname,
       MAX(CASE WHEN teacher_seqnum = 2 THEN t.lastname END) AS teacher2_lastname,
       MIN(day) as first_day,
       MAX(day) as last_day
FROM (SELECT cd.*,
             dense_rank() OVER (PARTITION BY course_id, teacher_id ORDER BY min_teacher_day) as teacher_seqnum
       FROM (SELECT c.description, cd.*,
                    t.firstname, t.lastname,
                    ROW_NUMBER() OVER (PARTITION BY cd.course_id ORDER BY cd.day) AS seqnum,
                    MIN(cd.day) OVER (PARTITION BY cd.course_id, cd.teacher_id) as min_teacher_day
             FROM Course_days cd JOIN
                  Teachers t
                  ON t.teacher_id = cd.teacher_id JOIN
                  Courses c
                  ON c.course_id = cd.course_id
            ) cd
     ) cd
GROUP BY course_id, description
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement