I have tables named course
, student
and students_in
in a MySQL database.
The tables look like this:
course
course_id name
3 Physics
12 English
19 Basket Weaving
4 Computer Science
212 Discrete Math
102 Biology
20 Chemistry
50 Robotics
7 Data Engineering
student
id name
2 Sally
1 Bob
17 Robert
9 Pierre
12 Sydney
41 James
22 William
5 Mary
3 Robert
92 Doris
6 Harry
students_in
course_id student_id grade
3 2 B
212 2 A
3 12 A
19 12 C
3 41 A
4 41 B
212 41 F
19 41 A
12 41 B
3 17 C
4 1 A
102 1 D
102 22 A
20 22 A
20 5 B
50 3 A
12 92 B
12 17 C
7 6 A
Since Sally took course IDs 3 and 212, the desired result would look like this (not the colorful table above, which I provided for illustration of the logic involved):
student_id student_name
12 Sydney <-- took course ID 3 with Sally
41 James <-- took course ID 3 and 212 with Sally
17 Robert <-- took course ID 3 with Sally
1 Bob <-- took course ID 4 with James
92 Doris <-- took course ID 12 with James and Robert
102 William <-- took course ID 102 with Bob
Other than using a recursive CTE
, is it possible to get the desired output using a more simplified approach, such as one or more of the following?
JOINS
andsubqueries
- using the
ANY
orIN
operators
Thank you!
CodePudding user response:
You can union the separate degrees and group/order the resulting names:
select `name` from (
(
select student.`name`
from student sally
join students_in sally_course
on sally.id = sally_course.student_id
join students_in si1
on sally_course.course_id = si1.course_id
join student
on si1.student_id = student.id
where student.`name` <> 'sally'
)
union
(
select student.`name`
from student sally
join students_in sally_course
on sally.id = sally_course.student_id
join students_in si1
on sally_course.course_id = si1.course_id
join students_in si2
on si1.course_id = si2.course_id
join student
on si2.student_id = student.id
where student.`name` <> 'sally'
)
union
(
select student.`name`
from student sally
join students_in sally_course
on sally.id = sally_course.student_id
join students_in si1
on sally_course.course_id = si1.course_id
join students_in si2
on si1.course_id = si2.course_id
join students_in si3
on si2.course_id = si3.course_id
join student
on si3.student_id = student.id
where student.`name` <> 'sally'
)) t
group by `name`
order by `name`
CodePudding user response:
For completeness, I'd add the recursive approach following with the two steps:
- base step: retrieves all courses which Sally participated to
- recursive step: retrieves all courses of students that have attended courses obtained at (n-1)th step
You can generalize how deep you want to go in the filtering clause of the recursive step (WHERE recursion_depth 1 <= <N>
).
WITH RECURSIVE cte AS (
SELECT s.student_id, s.course_id, 0 AS recursion_depth
FROM students_in s
INNER JOIN student ON s.student_id = student.id_
WHERE student.name_ = 'Sally'
UNION ALL
SELECT s2.student_id, s2.course_id, recursion_depth 1
FROM cte
INNER JOIN students_in s1 ON s1.course_id = cte.course_id
INNER JOIN students_in s2 ON s1.student_id = s2.student_id
WHERE recursion_depth 1 <= 3
)
SELECT DISTINCT student.*
FROM cte
INNER JOIN student ON cte.student_id = student.id_
WHERE NOT student.name_ = 'Sally'
Check the demo here.
Note: looking for a non-recursive query would translate to manually apply base step recursive step number of join operations, multiplied by the depth value, thing that the DBMS would do with a recursive query in a more natural and clean way.
CodePudding user response:
Use CTE
s for each of the levels with the operator IN
.
Finally use UNION
to get all the student ids (without duplicates) and again with the operator IN
get the students details from student
:
WITH
cte0 AS (SELECT id FROM student WHERE name = 'Sally'),
cte1 AS (SELECT student_id FROM students_in WHERE course_id IN (SELECT course_id FROM students_in WHERE student_id = (SELECT id FROM cte0))),
cte2 AS (SELECT student_id FROM students_in WHERE course_id IN (SELECT course_id FROM students_in WHERE student_id IN (SELECT student_id FROM cte1))),
cte3 AS (SELECT student_id FROM students_in WHERE course_id IN (SELECT course_id FROM students_in WHERE student_id IN (SELECT student_id FROM cte2))),
cte AS (SELECT student_id FROM cte1 UNION SELECT student_id FROM cte2 UNION SELECT student_id FROM cte3)
SELECT *
FROM student
WHERE id IN (SELECT student_id FROM cte) AND id <> (SELECT id FROM cte0);
See the demo.
If you want to use this solution just for comparison to a recursive query then fine, but it is obvious that this is not the proper way to do it.