In my MySQL database, I have three tables students
, classes
, courses
.
One class
has many students
.
One class
has many courses
.
The courses
table has one boolean field active
, and a string field name
.
So overall relationship is (sorry I am not sure how to better illustrate the relationship if it is not clear):
students (many_to_one) classes (one_to_many) courses
I have a function in Ruby that accepts an array of strings argument for the course name
s:
def get_student_names_whose_courses_are(active_course_names)
# Run a raw SQL query for the result
end
I would like to write a raw SQL query to get the names of students
whose courses
(via class
) matches exactly the passed in argument course names and are active.
For example, if active_course_names
holds values ['foo','bar']
. Student-A has active courses 'foo','bar','etc', student-B has active courses 'foo' and 'bar'. The raw quesry should only return student-B, even though student-A also has the two courses active, the point is an exact matching.
What I tried is this:
select stu.name
from students stu
inner join classes clz ON clz.id = stu.class_id
inner join courses cour ON cour.class_id = clz.id AND cour.name in (#{active_course_names,join("','")})
where cour.active = true;
But with this, it returns both student-A and student-B for the example above. How to make the query so that it returns students that have exactly the active courses?
I hope the following answer would be of help to your problem.
If you assign a rank in the same courses (for instance 'FOO'), then you can keep the courses which are repeated in the classes (rank>1) and apply your list of courses. After creating that, you can perform the join to bring the students' information that you need.
SELECT Temp.name,stud.id,stud.name,stud.class_id
FROM (
/*Create a view with the courses which are present with another classroom*/
SELECT T.id, T.name, T.active,T.class_id
FROM (
/*Create a view with only the active courses and their rank*/
SELECT id,name,active,class_id,
@course_rank := IF(@current_course = name,
@course_rank + 1, 1) AS course_rank,
@current_course := name
FROM courses,(select @current_course :=0,@course_rank :=0) r
WHERE active = 1
ORDER BY name, class_id ASC)T
/*Create a filter to bring only the classrooms with the same courses and the selected courses*/
WHERE T.course_rank>1 AND T.name IN ('foo','bar'))Temp
JOIN students stud
ON Temp.class_id=stud.class_id
Try this:
select distinct student from (
select stu.name student, cour.name course, count(cour.name )
from students stu
inner join classes clz ON clz.id = stu.class_id
inner join courses cour ON cour.class_id = clz.id
where cour.active = true
and cour.name in ('foo','bar')
group by stu.name , cour.name
having count(course) =2) A;
Here is the DEMO
In this DEMO only stu-A needs to be selected. Number 2 marks that student only has this two courses active and not 3 of them. Hope this is it...
This is a relational division problem with added "no remainder" condition. One of the simpler solution is this:
SELECT students.name, students.id
FROM students
JOIN classes ON students.class_id = classes.id
JOIN courses ON classes.id = courses.class_id
WHERE courses.active = true
GROUP BY students.name, students.id
-- if student is enrolled in both courses and no other then:
-- count(*) would be 2
-- count(course in foo, bar) would also be 2
HAVING COUNT(*) = 2
AND COUNT(CASE WHEN courses.name IN ('foo', 'bar') THEN 1 END) = 2
You can use group_concat like this:
SELECT
stu.name,
GROUP_CONCAT(cour.name ORDER BY cour.name ASC) AS courses
FROM students stu
inner join classes clz ON clz.id = stu.class_id
inner join courses cour ON cour.class_id = clz.id
where
cour.active = true
GROUP BY stu.name
HAVING GROUP_CONCAT(cour.name ORDER BY cour.name ASC) = 'bar,foo'
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.