An SQL query to get the exact matching results - mysql

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 names:
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?
My demo exapmle here

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

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'
DEMO IS HERE

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...

Related

Return all courses a student is taking (Many to Many SQL Database example)

I'm fairly new to MySQL, and trying to understand the many-to-many relationship since these examples can popup in interviews
There are 3 tables, and since a Student can have many courses and a Course can have many students, this is a Many-to-Many relationship right?
The tables are
Student- has student ID, name, date of birth, and department.
Courses- Has ID, Name of course
Student_Courses- Has student_id, course_id
How would I display these 2 questions-
1) Given a studentID, return all the names of the courses the student is taking
2) Return the name of students who is taking X amount of courses or more (Ex. 4 or more courses).
Im trying to write queries on these, but I'm stuck...
In the case of selecting all of the courses for a given student ID you could try the following, which will return one row for each Course a Student is associated with.
select
s.name as StudentName,
c.name as CourseName
from `Student` as s
inner join `Student_Course` as sc on (sc.student_id = s.ID)
inner join `Course` as c on (c.ID = sc.course_id)
where
(s.`ID` = 'given_Student_ID_here')
;
As for selecting a list of the names of Students taking N or more courses, for this you might use an aggregating sub-select as a WHERE clause in which we reference one of the outer tables (i.e. [Student]) so that the result of the aggregation is personalised per Student record:
select
s.name as StudentName
from `Student` as s
where
(
(
select count(*)
from `Student_Course` as sc
inner join `Course` as c on (c.ID = sc.course_id)
where (sc.student_id = s.ID)
) >= 4
)
;
You might also consider an alternative approach to this second problem by using the GROUP BY and HAVING clauses:
select
s.name as StudentName
from `Student` as s
inner join `Student_Course` as sc on (sc.student_id = s.ID)
inner join `Course` as c on (c.ID = sc.course_id)
group by
s.name
having
count(*) >= 4
;

SQL: Returning a list of all the classes that 2 specific students are both attending

I've tried all I can to figure this one out, hopefully someone here can help.
I'm given the database schema as follows:
Customer: ID FirstName LastName DateOfBirth
Instructor: Id FirstName LastName
FitnessClass: Id ClassName Cost InstructorId
Subscription: CustomerId ClassId StartDate
With the question being:
Retrieve a list of all classes (Id and ClassName) that both Joe Bloggs and John Snow have subscribed to (ie Joe Bloggs and John Snow have been in the same Fitness Class).
My attempts have only produced all classes that either are in, not only the classes that both are in.
My attempt:
SELECT fitnessclass.id, fitnessclass.classname
FROM fitnessclass
LEFT JOIN subscription ON fitnessclass.Id = subscription.ClassID
LEFT JOIN customer ON subscription.CustomerID = customer.ID
WHERE customer.ID IS NOT NULL
AND customer.FirstName IN ("Joe", "Bloggs")
We want the c.ClassId and c.ClassName values from DISTINCT rows where:
class c.Id is named c.ClassName and costs c.Cost and is taught by c.InstructorId
AND customer c1.ID is named c1.FirstName c1.LastName and was born on c1.DateOfBirth
AND customer c2.ID is named c2.FirstName c2.LastName and was born on c2.DateOfBirth
AND c1.ID <> c2.ID
AND customer s1.CustomerId subscribed to s1.ClassId starting on s1.StartDate
AND customer s2.CustomerId subscribed to s2.ClassId starting on s2.StartDate
AND c1.ID = s1.CustomerId AND c2.ID = s2.CustomerId
AND c1.FirstName = 'Joe' AND c1.LastName = 'Bloggs'
AND c2.FirstName = 'John' AND c2.LastName = 'Snow'
AND s1.ClassId = c.ClassId AND s2.ClassId = c.ClassId
Observe that an aliased table with no duplicates holds (the set of) rows that make a true statement from a statement template (predicate) associated with its base table by the database designer:
-- class c.Id is named c.ClassName and costs c.Cost and is taught by c.instructorId
FitnessClass c
-- cx.ID is named cx.FirstName cx.LastName and was born on cx.DateOfBirth
Customer cx
-- customer sx.CustomerId subscribed to class sx.ClassId starting on sx.StartDate
Subscription sx
Observe also that if expression L holds the rows satisfying templateL and expression R holds the rows satisfying templateR then
L JOIN R holds the rows satisfying templateL AND templateR
R WHERE condition holds the rows satisfying templateR AND condition
templateR ON condition holds the rows satisfying templateR AND condition
So the rows we want to SELECT from are:
FROM Class c
JOIN Customer c1 JOIN Customer c2
ON c1.ID <> c2.ID
JOIN Subscription s1 JOIN Subscription s2
WHERE
AND c1.ID = s1.CustomerId AND c2.ID = s2.CustomerId
AND c1.FirstName = 'Joe' AND c1.LastName = 'Bloggs'
AND c2.FirstName = 'John' AND c2.LastName = 'Snow'
AND s1.ClassId = c.ClassId AND s2.ClassId = c.ClassId
Conditions can be in ANDed any order as long as each only uses columns from a preceding JOIN or JOIN ON. So you can rearrange these in some other order if you think it better. (Eg to localize the use of some column names.) (But arguments that you must organize via ON (or that "," is inappropriate) are specious.)
DISTINCT removes duplicate rows after dropping the non-SELECTED columns from the table that the FROM etc produces. This is so that the result holds the set of rows that satisfy its template. DISTINCT isn't always necessary. But you still want distinct rows. In general you have to think about whether you can avoid DISTINCT. Sometimes you can't. Sometimes you can reason that what you do next with a table with duplicates gives the same answer whether or not there are duplicates. Rarely, the requested result is permitted to have or is supposed to have duplicates. But then you can't use that result further while reasoning via the simple relational-template expression correspondence. (Exercise: Show whether there is a SELECT returning the right classId & className values from the FROM etc table without DISTINCT.)
(It's not clear why you think that LEFT JOIN is appropriate. It returns what JOIN does but with unmatched left table rows extended by NULLs.)

Listing later dates in comparison to another entry in SQL

How would I list the students who started later than start year of student with id = 8871?
This is what I have so far:
SELECT sid s1
FROM Student
WHERE s1.started <= sid = '8871';
I went to this link for some reference but I'm still having issues:
Query comparing dates in SQL
Student Table has:
Student (Lastname, Firstname, SID, Started)
any help would be great
I'd use a self join where one side of the join contains the students you want and the the reference student 8871:
SELECT a.*
FROM student a
JOIN student b ON b.sid = '8871' AND a.started > b.started

Group SQL by newly defined parameters

i have gotten the task of creating a statistic from tables that look like this:
Faculty
1 FacultyName1
2 FacultyName2
3 FacultyName3
4 FacultyName4
5 FacultyName5
and this:
Student
1 StudentName1 FacultyNr2
2 StudentName2 FacultyNr3
3 StudentName3 FacultyNr5
4 StudentName4 FacultyNr2
now i have to create a statistic which Groups the Faculties into newly created fields and groups by them.
Say:
Faculty Group 1 Count: 3
Faculty Group 2 Count: 1
for this example lets say that all those of FacultyName1,FacultyName2,FacultyName3 should be listet as of "Faculty Group 1" and FacultyName4 and FacultyName5 as of "Faculty Group 2".
I started by doing the following:
Select Count(*)
FROM Student INNER JOIN Faculty on Student.FacultyID = Faculty.ID
But am stuck trying to understand how to Group, how i could create Groups in the Code, where i could just say: Group by FacultyGroups (Select Case When FacultyName = 'FacultyName1' = 'Faculty Group 1')
or something similiar, does anybody have any idea ?
Assuming that you have added a GroupID column in your Faculty table
SELECT COUNT(*), f.GroupID
FROM Student AS s
INNER JOIN Faculty AS f ON s.FacultyID = f.ID
GROUP BY f.GroupID
It gives you the number of student per group of faculties and the id of this group
There are better ways, but this should work:
SELECT
CASE
WHEN f.Name IN ('FacultyName1', 'FacultyName2', 'FacultyName3') THEN 'FacultyGroup1'
WHEN f.Name IN ('FacultyName4', 'FacultyName5') THEN 'FacultyGroup2'
END AS FacultyGroup,
COUNT(*) AS Students
FROM
Student s
INNER JOIN Faculty f ON s.FacultyID = f.ID
GROUP BY
CASE
WHEN f.Name IN ('FacultyName1', 'FacultyName2', 'FacultyName3') THEN 'FacultyGroup1'
WHEN f.Name IN ('FacultyName4', 'FacultyName5') THEN 'FacultyGroup2'
END;
If your "group" logic becomes too long then it will look messy in your query, so you might want to pre-calculate this. You could do this by using a sub-query for example, so one part of your query (the sub query) would convert faculties to groups and the other "main" part would count the students per group.

Getting data from multiple tables using joins

I have various tables like
Student
primary id , students name, course
Papers
paper id, papername, course, semester, type
StudentOptions
primary id, studentid (foreign key - reference student id) and paperid (foreign key - references paper id)
StudentsTerm
studentid (foreign key- references student id) and student semester
Now the kind of result i want is,
I want to choose a course then the term, which will give me the number of papers/subject it has with their types (Mandatory/Optional) and with that i want to have the count of number of students studying those papers from all these tables.
I don't wanna create any view or stuff, Just a normal select query will do.
The query i am running is :
SELECT p_name,
p_id,
type,
Count(sps.studentid) AS counts
FROM students,
str,
papers
LEFT JOIN sps
ON sps.paperid = papers.p_id
WHERE sps.studentid = students.studentid
AND students.studentid = str.studentid
AND sps.studentid = str.studentid
AND str.semesterid = p_semid
AND str.sessionid = 12
AND students.course = c_id
AND c_id = 6
AND p_semid = 1
GROUP BY p_id
As better practice, if you are going to be using explicit JOIN syntax, then don't use implicit syntax. In the query you posted above, you select from papers, but you don't use it anywhere. Also, your column names are slightly ambiguous. If it's easier, alias each table using a single letter, or explicitly prefix the column names. If you're using an aggregate with GROUP BY, you cannot select columns which are not in the group.
Let's assume this is your ER diagram:
Let's first join all the tables:
SELECT a.id, a.name
FROM student a
JOIN str b ON b.student_id = a.id
JOIN sps c ON c.student_id = a.id
JOIN papers d ON d.id = c.paper_id
Now you wish to find the number of students studying papers from a specific course and type:
SELECT a.id, a.name
FROM student a
JOIN str b ON b.student_id = a.id
JOIN sps c ON c.student_id = a.id
JOIN papers d ON d.id = c.paper_id
WHERE b.semester = 12
AND d.course = 6
Because your attributes are ambigiuous, it is hard to tell what tables they are coming from. If you can set up the structure and sample data on SQL Fiddle, we could help you better.