I have a struggle in adding this two queries in one query - mysql

Select s.SID , s.Lastname, s.Firstname, s.MI, g.YearLevel, g.Course,
count(s.SID) As 'Number Of Passed Subject'
From student s
inner Join grades g on g.StudentID = s.SID
Where g.Completion = 'Passed' and g.YearLevel = '4th Year'
group by g.YearLevel, g.Course
order by g.YearLevel, g.Course;
Select s1.SID , s1.Lastname, s1.Firstname, s1.MI, g1.YearLevel, g1.Course,
SUM(su.LecUnit) As 'Total of Units Passed'
From grades g1
INNER Join student s1 ON s1.SID = g1.StudentID
INNER Join subjects su ON su.SubjectCode = g1.SubjectCode
WHERE g1.Completion = 'Passed' AND g1.YearLevel = '4th Year'
GROUP BY g1.Course, g1.YearLevel
order by g1.Course, g1.YearLevel;

Use the second query as the starting point, and then add a distinct count of student courses for the number of passed subjects. The logic here is that the additional join to the subjects table might duplicate each intermediate record from the first two joins. Taking the distinct count removes this possible duplication.
Select s1.SID, s1.Lastname, s1.Firstname, s1.MI,
COUNT(DISTINCT g1.Course) As `Number Of Passed Subject`,
SUM(su.LecUnit) As `Total of Units Passed`
FROM student s1
INNER JOIN grades g1 ON s1.SID = g1.StudentID
INNER JOIN subjects su ON su.SubjectCode = g1.SubjectCode
WHERE g1.Completion = 'Passed' AND g1.YearLevel = '4th Year'
GROUP BY 1
ORDER BY 1;
Note that I am also grouping only by s1.SID. Assuming SID be the primary key from that table, it covers all columns from the student table. The other table fields you were selecting probably don't belong there.

Related

Rewriting a query that has two sub queries using no sub queries

Given the database schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select cname, Province
From Customer c
Where exists (
Select *
from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple'
)
and Not exists (
Select *
from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple'
)
How would I go about rewriting this query without the two sub queries?
You can use the LEFT JOIN/NULL pattern to find customers who haven't bought any non-Apple products. Then you can do this all with just joins. You'll have to join with Supply and Parts twice, once for finding Apple products, then again for excluding non-Apple products.
SELECT distinct c.name, c.province
FROM Customer AS c
JOIN Supply AS s1 ON s1.cid = c.cid
JOIN Parts AS p1 ON p1.pid = s1.pid
LEFT JOIN Supply AS s2 ON s2.cid = c.cid
LEFT JOIN Parts AS p2 ON p2.pid = s2.pid AND p2.producer != 'Apple'
WHERE p1.producer = 'Apple' AND p2.pid IS NULL
Notice that in the LEFT JOIN you put restrictions of the second table in the ON clause, not the WHERE clause. See Return row only if value doesn't exist for more about this part of the query.
You want customer who only bought Apple products?
One possible solution is based on conditional aggregation:
Select c.cname, c.Province
From Customer c
join
( -- this is not a Subquery, it's a Derived Table
Select s.CId -- assuming there's a CId in Supply
from Supply s
join Part p
on p.pId = s.pId
group by s.CId
-- when there's any other supplier this will return 1
having max(case when p.Producer = 'Apple' then 0 else 1 end) = 0
) as p
on p.CId = c.CId

How do I query a set that doesn't contain any members from another set in MySQL?

I'd like to return a list of students who have taken classes in one department but not in another. Here's my query and its result is coming up blank for some reason.
SELECT *
FROM student
JOIN transcript
ON student.id = transcript.studID
JOIN course
ON transcript.crsCode = course.crsCode
WHERE deptId = "CSCI" NOT IN
(
SELECT student.name
FROM student
JOIN transcript
ON student.id = transcript.studID
JOIN course
ON transcript.crsCode = course.crsCode
WHERE deptId = "MATH"
);
Here are what the tables look like:
Student (id, name, address, status)
Transcript (studId, crsCode, semester, grade)
Courses (crsCode, deptId, crsName, descr)
Without using sub queries:-
SELECT DISTINCT student.*
FROM student
JOIN transcript
ON student.id = transcript.studID
INNER JOIN course c1
ON transcript.crsCode = c1.crsCode
AND c1.deptId = 'CSCI'
LEFT OUTER JOIN course c2
ON transcript.crsCode = c2.crsCode
AND c2.deptId = 'MATH'
WHERE c2.crsCode IS NULL
This is joining student against transcript. It then joins against course twice, once for the course you want and a LEFT OUTER JOIN for the course you don't want. The WHERE clause checks that there was no match on the course you do not want.
The DISTINCT is used to limit the results to single occurrences. This may not be necessary, but that depends on whether a single student can have done courses multiple times.
You could use two exists conditions - one for the department you want to include and one for the department you want to exclude.
SELECT s.*
FROM student s
WHERE EXISTS (SELECT *
FROM transcript t
JOIN courses c ON t.crsCode = c.crsCode
WHERE deptId = 'MATH' AND t.studId = s.id) AND
NOT EXISTS (SELECT *
FROM transcript t
JOIN courses c ON t.crsCode = c.crsCode
WHERE deptId = 'CSCI' AND t.studId = s.id)
To check two conditions you need to add AND clause in your query and check student.name is NOT IN in your sub-query as:
SELECT *
FROM student
JOIN transcript
ON student.id = transcript.studID
JOIN course
ON transcript.crsCode = course.crsCode
WHERE deptId = "CSCI" AND student.name NOT IN
(
SELECT student.name
FROM student
JOIN transcript
ON student.id = transcript.studID
JOIN course
ON transcript.crsCode = course.crsCode
WHERE deptId = "MATH"
);

How to write a query with count

I have two tables as follows:
==================
StudentsClasses
----------------
ID (Registration ID of the class)
StudentID (ID of student taking class)
ClassID (ID of certain class)
----------------
==================
Students
---------------
ID (ID of student)
Name (Name of student)
GradeLevelID (Grade of student)
---------------
==================
And they are joined by StudentsClasses.StudentID and Students.ID.
I am trying to write a query to return the students with the least classes registered. My query is:
SELECT Students.Name, COUNT(StudentsClasses.StudentID) AS Expr1
FROM StudentsClasses INNER JOIN
Students ON StudentsClasses.StudentID = Students.ID
GROUP BY StudentsClasses.StudentID, Students.Name
ORDER BY Expr1
However, that only returns all the students with at least 1 class in ASC order.
I know the correct answer is 7 students with 0 classes.
How can I modify my query to return only those 7 students with 0 classes.
To enlist those students, that have no classes, instead of INNER JOIN you should be using LEFT JOIN here, to make sure all rows from students table are listed, even though there is no rows in studentclasses for that particular student.
SELECT
s.name, count(sc.id) AS classes
FROM
students s
LEFT JOIN studentsclasses sc ON s.id = sc.studentid
GROUP BY s.name
HAVING count(sc.id) = 0 -- added after comment
ORDER BY count(sc.id);
OR another method (for retrieving only students that have 0 classes):
SELECT
s.name
FROM
students.s
LEFT JOIN studentsclasses sc ON s.id = sc.studentid
WHERE
sc.id IS NULL
This should limit your results to those students having whatever the minimum number of registered classes is (so if the minimum is currently zero, then zero, if it becomes true that the least number of registrations is 3, then it will use 3, etc.)
select s.name,
v.classes
from students s
join (
select s.name,
count(sc.id) as classes
from students s
left join studentsclasses sc
on s.id = sc.studentid
group by s.name
order by count(sc.id)
limit 1
) v
on s.name = v.name
Answers for this question.
How can I modify my query to return only those 7 students with 0 classes.
SELECT
s.name, count(sc.id) AS classes
FROM
students s
LEFT JOIN studentsclasses sc ON s.id = sc.studentid
where (SELECT count(sc.id) FROM
students s
LEFT JOIN studentsclasses sc ON s.id = sc.studentid)=0
GROUP BY s.name
ORDER BY count(sc.id);

Matching one value in one column with more than one values in other column in SQL

Suppose I have three tables
Student Student_Interest Interest
======= ================ ========
Id Student_Id Id
Name Interest_Id Name
Where Student_Interest.Student_Id refers to Student.Id
and Student_Interest.Interest_Id refers to Interest.Id
Let's say we have three kinds of interest viz. "Java", "C", "C++" and "C#" and there are some entries in the student table and their respective interest mapping entries in the Student_Interest table. (A typical many-to-many relationship)
How can we get the list of students that have both "Java" and "C" as their interests?
I already see a couple of correct answers, but anyway, here is my one:
select s.* from student s
join (
select si.student_id from student_interest si join interest i on i.id = si.interest_id
where i.name in ('Java','C') group by si.student_id having count(*) = 2
) iv on iv.student_id = s.id
Simply get the Java and C records from student_interest, group by student and see if you get the complete number of interests for a student. With such students found you can display data from the student table.
select *
from student
where id in
(
select student_id
from student_interest
where interest_id in (select id from interest where name in ('Java', 'C'))
group by student_id
having count(distinct interest_id) = 2
);
EDIT: You've asked me to show a query with EXISTS. The straight-forward way would be:
select *
from student
where exists
(
select *
from student_interest
where student_id = student.id
and interest_id = (select id from interest where name = 'Java')
)
and exists
(
select *
from student_interest
where student_id = student.id
and interest_id = (select id from interest where name = 'C')
);
For every interest an additional EXISTS clause. If, however, you want to convert the IN query above to an EXISTS query, so to have only one EXISTS clause, you get:
select *
from student
where exists
(
select student_id
from student_interest
where student_id = student.id
and interest_id in (select id from interest where name in ('Java', 'C'))
group by student_id
having count(distinct interest_id) = 2
);
I find the IN clause more readable, but that's a matter of taste, I guess.
How can we get the list of students that have both "Java" and "C" as
their interests?
We can write t(t.c,...) to say that row (t.c,...) is in table t. Let's alias Student to s, Student_Interest to sij and sic and Interest to ij and ic. We want rows (s.Id,s.Name) where
s(s.Id,s.Name)
AND sij(sij.Student_Id,sij.Interest_Id) AND s.Id = sij.Student_Id
AND sic(sic.Student_Id,sic.Interest_Id) AND s.Id = sic.Student_Id
AND ij(ij.Id,ij.Name) AND ij.Id=sij.Interest_Id AND ij.Name = 'Java'
AND ic(ic.Id,ic.Name) AND ic.Id=sic.Interest_Id AND ic.Name = 'C'
So:
select s.Id,s.Name
from Student s
join Student_Interest sij on s.Id = sij.Student_Id
join Student_Interest sic on s.Id = sic.Student_Id
join Interest ij on ij.Id=sij.Interest_Id AND ij.Name = 'Java'
join Interest ic on ic.Id=sic.Interest_Id AND ic.Name = 'C'
You will need to join all the three tables to get the information as below:
SELECT s.*
FROM Student s, Student_Interest si, Interest i, Interest sin
WHERE s.Id = si.Student_Id
AND i.Id = si.Interest_Id
AND sin.Id = si.Interest_Id
AND i.Name = 'Java'
AND sin.Name = 'C'
Do a JOIN with all the tables. You need to GROUP BY the column Name to get both interest like below. See a demo fiddle http://sqlfiddle.com/#!2/e80391/13
select s.Name
from student s
join Student_Interest si on s.id = si.Student_Id
join Interest i on si.Interest_Id = i.id
join Interest ii on si.Interest_Id = ii.id
group by s.Name
having count(*) > 1

SQL query - dynamic sub query

I am having trouble trying to create a query to:
Select all the students who have not completed all peer review's for a particular week.
background: Each week, every student must peer review their peers in the same group.
Each group can be a different size, which is the problem I am having.
this is my current test data:
Table 1: peer review table
Table 2: student table.
This is my inital query, groups all the students based on the amount of peer review's they've made. I now need to to check if the count(*) is less than the size of the group for each student :
SELECT *
FROM peerreview
RIGHT JOIN student
ON student. studentID = peerreview.reviewer
WHERE week = 11
GROUP BY studentID
HAVING Count(*) < ????
Following query will return the student which has reviewed all the students in same group.
SELECT a.reviewer,
a.groupid
FROM (SELECT student2.studentID AS reviewer,
student1.groupid,
Count(*) AS cnt
FROM student student1
INNER JOIN peerreview
ON student1.studentID = peerreview.reviewee
INNER JOIN STUDENT STUDENT2
ON student2.studentID = peerreview.reviewer
WHERE student2.groupid = student2.groupid
AND peerreview.week = 11
GROUP BY student1.groupid,
student2.studentID) a
INNER JOIN (SELECT groupid,
Count(*) - 1 AS cnt
FROM student
GROUP BY groupid) b
ON a.groupid = b.groupid
AND a.cnt = b.cnt
See SqlFiddle
Select S.StudentId As Reviewer
, S1.StudentId As StudentYetToBeReviewed
, Weeks.WeekNum
From Student As S
Join Student As S1
On S1.GroupId = S.GroupId
And S1.StudentId <> S.StudentId
Cross Join (
Select 7 As WeekNum
Union All Select 11
) As Weeks
Where Not Exists (
Select 1
From PeerReview As P1
Where P1.reviewee = S1.StudentId
And P1.Week = Weeks.WeekNum
)
Order By WeekNum, reviewer
This provides you a list, by week, of the reviewer and the person they need to review. In the real solution, you would want to replace the Cross Join of weeks with a distinct list of weeks in which reviews should happen.
SQL Fiddle version
select distinct s1.*
from student s1 inner join student s2 on s1.groupId = s2.groupeId
left join peerreview pr on pr.revieweer = s1.studentId
and pr.reviewee = s2.studentId
where pr.Week = ? and pr.revieweer is null and s1.studentId <> s2.studentId