Where is the error in my nested MySQL query? - mysql

I am guessing there is a mistake in the nested query however I cannot seem to find it myself.
Here is the query:
Select student.sid, name
from student join exam on exam.sid = student.sid
where student.sid in (select *
from course join exam on cid=courseid
group by exam.sid
having sum(credits) >= 20)
Thank you in advance!

You can use the group by as follows:
select s.sid, s.name
from student s
Join exam e on s.sid = e.sid
Join course c on c.cid = e.courseid
group by s.sid, s.name
having sum(c.credits) >= 20

Yes, there is a mistake. The nested query is returning multiple columns but the comparison is to only a single column.
This will obviously generate an error. Presumably, you don't want the sum() but without sample data, desired results, and an explanation of what the query is supposed to be doing, it is hard to make a concrete suggestion.
The query may have other errors, but presumably, you intend something like this:
select s.sid, s.name
from student s
where s.sid in (select e.sid,
from course c join
exam e
on c.cid = e.courseid
group by e.sid
having sum(c.credits) >= 20
)

Related

How to combine two sql queries such that the output of one query becomes a condition for another

I have two SQL queries
select a.name from Subjects a join Courses b on(a.id=b.subject) group by a.name having count(b.id) >=20;
this returns names of subjects with 20 or more course offerings.
select course from course_enrolments group by course having count(student)<20;
this returns the courses with less than 20 students enrolled in it.
How can I combine both the queires so that i can get names of subjects with 20 or more offerings and less than 20 students enrolled in it?
Courses.id=Course_enrolment.course can be used to join table Courses and Course_enrolments
Any help will be appreciated.
Try this:
select a.name
from Subjects a join Courses b on(a.id=b.subject)
where b.id in (select course
from course_enrolments
group by course
having count(student)<20
)
group by a.name
having count(b.id) >=20;
Try this:
SELECT
FROM (SELECT a.name,
b.id as courseid
FROM Subjects a
JOIN Courses b
ON a.id = b.subject
GROUP BY a.name
HAVING count(a.id) >= 20) s
JOIN (SELECT course
FROM course_enrollments ce
GROUP BY course
HAVING count(*) < 20) c
ON c.course = s.courseid

Proper way to write a MySQL query that uses a derived column and multiple joins

This query is NOT legal syntax and I'm trying to understand what the efficient way of writing it is. This is what I have:
SELECT a.*, b.id, lapsed FROM
( SELECT DATEDIFF(CURDATE(), MAX(day)) AS lapsed FROM c ) AS x
FROM first_table a
INNER JOIN second_table b ON a.id = b.some_id
INNER JOIN third_table c ON c.user_id = a.user_id
WHERE a.some_col = 'm'
AND b.num >= lapsed
There's three tables being joined. Normally this would be trivial, but the problem is my last part of the WHERE clause, specifically b.num >= lapsed is doing a comparison on a derived value. Is there a correct way to write this?
Haven't tested this, but if the subquery is correct then this should work.
I also assumed that the 'c' in the example of the question is also referring to that third_table and not some table/view called c.
And the INNER JOIN to third_table was commented out, since it's mostly useless to INNER JOIN table/views when you don't use any fields of it. Well, it could be used to limit on records that are in that table, but most often it's just useless to do that.
SELECT a.*, b.id, x.lapsed
FROM first_table a
INNER JOIN second_table b ON a.id = b.some_id
--INNER JOIN third_table c ON c.user_id = a.user_id
LEFT JOIN (
SELECT DATEDIFF(CURDATE(), MAX(day)) AS lapsed
FROM third_table
) AS x ON (1=1)
WHERE a.some_col = 'm'
AND b.num >= x.lapsed;
Given the MAX() aggregate, it looks like you want the latest day value for each user_id value from third_table.
To get that, we can write a query like this:
SELECT c.user_id
, DATEDIFF(CURDATE(),MAX(c.day)) AS lapsed
FROM third_table c
GROUP BY c.user_id
You can use the resultset from this query as a rowsource in another query, by using that query as a inline view in place of a table reference. (MySQL refers to an inline view as a "derived table".)
We just wrap that query in parens, and use it in place of where we would normally find a table reference. The inline view (derived table) will need to be assigned an alias.
For example:
SELECT a.*
, b.id
, d.lapsed
FROM first_table a
JOIN second_table b
ON b.some_id = a.id
JOIN ( SELECT c.user_id
, DATEDIFF(CURDATE(),MAX(c.day)) AS lapsed
FROM third_table c
GROUP BY c.user_id
) d
ON d.user_id = a.user_id
WHERE a.some_col = 'm'
AND b.num > d.lapsed
A query similar to that should run. But whether that returns the resultset you expect to achieve, that really depends on what result you are attempting to return. Absent a specification (apart from some invalid query syntax), we're just guessing.

Is this the right way to join tables to fetch data?

I have a database with the tables:
Student(SID,Name,Surname,Age)
Registration(StudentID,CourseID)
Course(CID,Name,Cost)
I would like to extract only the name of the courses with students younger than 20. Will the query below do just that?
SELECT C.NAME
FROM Course C
INNER JOIN Registration
INNER JOIN Student S
WHERE CID = CourseID
AND SID = StudentID
AND Age < 20
GROUP BY C.NAME
I would also like to extract the number of students in each course having students younger than 20. Is it correct to do it as below?
SELECT count(S.NAME)
,C.NAME
FROM Student S
INNER JOIN Course C
INNER JOIN Registration
WHERE Age < 20
AND CID = CourseID
AND SID = StudentID
GROUP BY C.NAME
You are missing the ON part for the join otherwise it would just be a CROSS JOIN.
Your first query should look like this if you want just a distinct list of student names:
SELECT DISTINCT C.NAME
FROM Course C
INNER JOIN Registration R ON C.CID = R.CourseID
INNER JOIN Student S ON R.StudentID = S.SID
WHERE Age < 20
Your second query shouldn't really have the C.Name in the select if you want to get just a count unless you want a count of how many students have that name.
SELECT count(*)
FROM Student S
INNER JOIN Registration R ON s.SID = R.StudentID
INNER JOIN Course C ON c.CID = R.CourseID
WHERE Age < 20
GROUP BY C.NAME
First join these tables, then group by Course's PK(CID), Add the HAVING condition to filter the course which has students younger than 20.
Then use Course table to join the result to get the course name and count of students in the course.
SELECT
T1.Name,
T2.StudentCount
FROM
Course T1
INNER JOIN (
SELECT
c.CID,
COUNT(s.SID) AS StudentCount
FROM
Course c
LEFT JOIN Registration r ON c.CID = r.CourseID
LEFT JOIN Student s ON s.SID = r.StudentID
GROUP BY c.CID
HAVING COUNT(IF(s.Age < 20, 1, NULL)) > 0
) T2 ON T1.CID = T2.CID
More correctly, you should move the conditions of the join, to the join statements themselves by including them in the on clause instead of the where. While the results may not change in this instance, if you were to start including outer joins you would encounter difficulties.
SELECT count(S.NAME)
,C.NAME
FROM Student S
INNER JOIN Registration R
ON s.SID = R.StudentID
INNER JOIN Course C
ON c.CID = R.CourseID
WHERE Age < 20
GROUP BY C.NAME
There's a fiddle here showing it in action: http://sqlfiddle.com/#!9/c3b8f/1
Your first query will also produce the results you want, but again, you should move the join predicates to the join itself. Also, you don't need to perform the grouping just to get distinct values, mysql has an expression for that called distinct. So rewritten, the first query would look like:
SELECT DISTINCT C.NAME
FROM Student S
INNER JOIN Registration R
ON s.SID = R.StudentID
INNER JOIN Course C
ON c.CID = R.CourseID
WHERE Age < 20.
Again, the results are the same as what you have already but it is easier to 'read' and will put you in good stead when you move on to other queries. As it stands you have mixed implicit and explicit join syntax.
This fiddle demonstrates both queries: http://sqlfiddle.com/#!9/c3b8f/4
edit
I may have misinterpreted your original question - if you want the total number of students enrolled in a course with at least one student under 19, you can use a query like this:
select name, count(*)
from course c
inner join registration r
on c.cid = r.courseid
where exists (
select 1
from course cc
inner join registration r
on cc.cid = r.courseid
inner join student s
on s.sid = r.studentid
where cc.cid = c.cid
group by cc.cid
having min(s.age) < 20
)
group by name;
Again with the updated fiddle here: http://sqlfiddle.com/#!9/c3b8f/17

MySQL Left Join Count Not working

I have 2 tables (members, duty_sched)
I want to be able to count how many time each member appears in the duty_sched table.
This is what I have after several weeks searching
SELECT
members.fname,
members.lname,
(SELECT COUNT(duty_sched.id) FROM duty_sched
WHERE 'duty_sched.mbr_id' = 'members.mbr_id') AS shifts
FROM members
ORDER BY members.lname, members.fname
The output I'm getting is all zeros, what I would like it to show me is:
Name shifts
Bob Smith 4
Jane Johnson 2
Any help I can get will be greatly appreciated.
Remove the single quotes: WHERE duty_sched.mbr_id = members.mbr_id)
You can also write your query with a LEFT JOIN:
SELECT
m.fname,
m.lname,
COUNT(d.id) AS shifts
FROM members AS m
LEFT JOIN duty_sched AS d
ON d.mbr_id = m.mbr_id
GROUP BY
m.mbr_id -- the Primary Key of `members`
ORDER BY
m.lname, m.fname ;
You can try something like that
SELECT
members.fname,
members.lname,
count(*) FROM duty_sched JOIN members
WHERE duty_sched.mbr_id = members.mbr_id
Group by duty_sched.mbr_id
ORDER BY members.lname, members.fname
Try this:
SELECT m.fname, m.lname, IFNULL(A.shifts, 0) shifts
FROM members m
LEFT JOIN (SELECT mbr_id, COUNT(id) shifts
FROM duty_sched GROUP BY mbr_id) A ON m.mbr_id = A.mbr_id
ORDER BY m.lname, m.fname;

Optimize query selecting elements from 3 tables mysql

The database is as follows:
Classes Challenges Class Challenges
id id id
title class_id
challenge_id
In order to get all the challenges of a specific class I use the following
SELECT
DISTINCT class_challenges.challenge_id,
challenges.title
FROM class_challenges
LEFT JOIN challenges
ON class_challenges.challenge_id = challenges.id
WHERE class_challenges.class_id = :class_id
ORDER BY challenge_id
How can I do the same for all the challenges that do not belong to a specific class?
So far I use:
SELECT
DISTINCT challenges.id,
challenges.title
FROM
challenges,
class_challenges
WHERE challenges.id NOT IN(
SELECT
DISTINCT class_challenges.challenge_id
FROM class_challenges
LEFT JOIN challenges
ON class_challenges.challenge_id = challenges.id
WHERE class_challenges.class_id = :class_id
ORDER BY challenge_id
);
which I think can be written better. (maybe using a double join?)
So, how can this be optimized (if it can?)
Try this query
SELECT
t.id,
t.title,
t.CCID
FROM
(
SELECT
challenges.id,
challenges.title,
class_challenges.id as CCID
FROM
challenges
LEFT JOIN class_challenges
ON class_challenges.challenge_id = challenges.id
) as t
WHERE t.CCID IS NULL
Without seeing your expected results it's a bit vague for me to answer, but anyway here is a code to try out. Call me visual ;) Please comment after you have tried out the query.
The code list down 'challenges for each class'. You may use a variable to filter out the data for a specific class id.
SQLFIDDLE DEMO
SELECT DISTINCT a.id,
group_concat(b.challenge_id) as challengeIs,
group_concat(c.title) as Titles
FROM Classes a
LEFT JOIN
class_challenges b
ON a.id = b.class_id
LEFT JOIN challenges c
ON b.challenge_id = c.id
group by a.id
ORDER BY a.id;
Results:
ID CHALLENGEIS TITLES
100 11,15 a,c
200 15 b
300 11,15 a,c
400 (null) (null)
500 15 b
Challenges that do not belong to a specific class
Just noticed that I have missed out to add this portion of the query.
Query:
-- challenge that doesn't belong to a class
SELECT DISTINCT c.id,
c.title, group_concat(a.id) as class
FROM challenges c
LEFT JOIN
class_challenges b
ON b.challenge_id = c.id
LEFT JOIN Classes a
ON a.id = b.class_id
GROUP BY c.id
HAVING class is null
ORDER BY c.id;
Results:
ID TITLE CLASS
18 c (null)
How about this, using an anti-join:
SELECT challenges.id, challenges.title
FROM challenges
LEFT JOIN class_challenges ON class_challenges.challenge_id = challenges.id
AND class_challenges.class_id = :class_id
WHERE class_challenges.id IS NULL