How to count MySQL results in a has-many-through relation - mysql

There is pretty good article how to filter results in a has-many relation:
How to filter SQL results in a has-many-through relation
I'm just seeking a solution for COUNT result, not show them all.
student {
id
name
}
club {
id
name
}
student_club {
student_id
club_id
}
How many students are in both CLUB1 && CLUB2?
EDIT:
It would be great to use "Martin 2" method from a link below:
SELECT s.stud_id, s.name
FROM student s
JOIN student_club sc USING (stud_id)
WHERE sc.club_id IN (30, 50)
GROUP BY 1,2
HAVING COUNT(*) > 1;
Just adding something to COUNT results.

Probably simplest, cleanest and fastest for just two students:
SELECT count(*) AS ct
FROM student_club x
JOIN student_club y USING (stud_id)
WHERE x.club_id = 30
AND y.club_id = 50;
You don't need to join to the student table for this at all - as soon as you know the list of stud_id you are interested in.
For any number of students, Martin's query is more convenient. You can simplify in a similar fashion:
SELECT count(*) AS ct
FROM (
SELECT stud_id
FROM student_club
WHERE club_id IN (30, 50)
GROUP BY 1
HAVING count(*) = 2 -- adapt to number of items in list
) x;
Requires that (stud_id, club_id) is unique of course, and that list items are also unique.

The query uses table aliases for tables student_club and club. This allows to return rows only for students who are in both clubs. Then, using COUNT allows to return the number of students:
SELECT COUNT(*) AS nb
FROM student s, student_club sc1, club c1, student_club sc2, club c2
WHERE s.id=sc1.student_id AND sc1.club_id=c1.id AND c1.name="CLUB1"
AND s.id=sc2.student_id AND sc2.club_id=c2.id AND c2.name="CLUB2"
If you really want to use the "Martin 2" query, you may count the number of records this way:
SELECT COUNT(*) AS nb
FROM (
SELECT s.stud_id, s.name
FROM student s
JOIN student_club sc USING (stud_id)
WHERE sc.club_id IN (30, 50)
GROUP BY 1,2
HAVING COUNT(*) > 1
) tmp;

Related

MySQL query to return rows ordered by averages from another table

I am building a database for a contest in which people upload photos and the jury grades them. I have two tables: photographs (id, name, user_id and section_id) and grades (id, grade, photo_id, juror_id).
What I want to achieve is a query to return all photos ordered by the average of all grades given to each photo.
For example, if we have 2 photographs with ids 1 and 2 with photo 1 having two grades (5, 6) and photo 2 also having two grades (8, 10) the first returned row will be the photo with id 2 (the average of the grades is 9 and it is greater than 5.5, the average of photo 1).
How could I achieve this?
Here is a pseudo-example of a query
SELECT * FROM photographs ORDER BY AVERAGE(SELECT grade FROM grades)
This is a job for AVG() and GROUP BY.
To get the average grade by photo from your grades table this subquery does it.
SELECT AVG(grade) avg_grade,
photo_id
FROM grades
GROUP BY photo_id
That subquery is guaranteed to return exactly one row per photo_id value. So you can LEFT JOIN it to your photographs table like so.
SELECT avg_grade.avg_grade,
photographs.*
FROM photographs
LEFT JOIN (
SELECT AVG(grade) avg_grade,
photo_id
FROM grades
GROUP BY photo_id
) avg_grade ON photographs.id = avg_grade.photo_id
ORDER BY avg_grade.avg_grade DESC
First of all you need to join your table properly, then to agreagat result, and after that to order output:
SELECT
p.id,
AVG(g.grade) AS averageGrade
FROM photographs AS p
JOIN grade AS g ON g.photo_id = p.id
GROUP BY p.Id
ORDER BY AVG(g.grade) DESC

SQL: Selecting all tuples which contain a certain value in a column after using count

I have the following table called Stores:
Name | Category | Industry
ABC appliances retail
XYZ banking finance
NZE clothing retail
JKI tutoring education
I would like to output all the Names that are the only one in their Industry (e.g. XYZ and JKI are the only Names in their Industry).
I have the following query:
select s.Name, s.Industry, a.Number
from Stores s
inner join (
select Industry, count(*) as Number
from Stores group by Industry
) a
on s.Industry = a.Industry;
I get an output table which has an attribute called Number which gives the total number of times each Industry appears in the table Stores. How can I select all the tuples which have the value of 1 in the Number column after using the inner join?
use where condition
select s.Name, s.Industry, a.Number
from Stores s
inner join (
select Industry, count(*) as Number
from Stores group by Industry
) a
on s.Industry = a.Industry where a.Number=1
use corelated subquery
select s.* from stores s
where exists ( select 1 from Stores s1 where s.Industry=s1.Industry
having count(*)=1)
You can use EXISTS :
SELECT s.*
FROM Stores s
WHERE EXISTS (SELECT 1 FROM Stores s1 WHERE s1.Industry = s.Industry AND s1.Name <> s.Name);
I would just use aggregation:
select industry, max(name) as name
from stores
group by industry
having count(*) = 1;
If there is only one name, then max(name) is the one.
If names can be repeated in the table, then:
having min(name) = max(name)

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
;

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.

MySQL query the largest amount present in a collection

I have a table Teacher that contains a TeacherPIN as well as a table Student that contains a TeacherPIN referencing a Teacher. The idea is that a Teacher contains a certain amount of Students.
I am tasked with finding the teacher with the most students. I am currently using the query:
select t.TeacherPIN, count(s.TeacherPIN)
from Teacher t, Student s
where t.TeacherPIN = s.TeacherPIN
and ((select count(s1.TeacherPIN) from Student s1 where s1.TeacherPIN = t.TeacherPIN) >=
(select count(s2.TeacherPIN) from Student s2 where s2.TeacherPIN = (select t1.TeacherPIN from Teacher t1)));
I'm sure I'm making this way more complicated than I should be, but I've been at it for a while now and am hoping someone could push me in the right direction.
Thanks!
To find just one teacher (of possibly many) with maximum number of students:
SELECT TeacherPIN
, COUNT(*) AS NumberOfStudents
FROM Student
GROUP BY TeacherPIN
ORDER BY NumberOfStudents DESC
LIMIT 1
To find all of them:
SELECT TeacherPIN
, COUNT(*) AS NumberOfStudents
FROM Student
GROUP BY TeacherPIN
HAVING COUNT(*) =
( SELECT COUNT(*) AS NumberOfStudents
FROM Student
GROUP BY TeacherPIN
ORDER BY NumberOfStudents DESC
LIMIT 1
)