I have 4 tables, I would like to select one column from each table, but only if the department has both 'Mick' and 'Dave working in it (must have both names, not one or the other). But it does not seem to be working properly:
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM STUDENTS
NATURAL JOIN SCHOOLS NATURAL JOIN TOWNS NATURAL JOIN
COUNTIES
WHERE FIRST_NAME IN ('Mick','Dave)
/
I'm going wrong somewhere (probably lots of places :( ). Any help would be great
Don't use NATURAL JOIN. It is an abomination, because it does not take properly declared foreign key relationships into account. It only looks at the names of columns. This can introduce really hard to find errors.
Second, what you want is aggregation:
select sc.SCHOOL_NAME, t.TOWN, c.COUNTY
from STUDENTS st join
SCHOOLS sc
on st.? = sc.? join
TOWNS t
on t.? = ? join
COUNTIES c
on c.? = t.?
where FIRST_NAME in ('Mick', 'Dave')
group by sc.SCHOOL_NAME, t.TOWN, c.COUNTY
having count(distinct st.first_name) = 2;
The ? are placeholders for table and column names. If you are learning SQL, it is all the more important that you understand how columns line up for joins in different tables.
A where clause can only check the values in a single row. There is a separate row for each student, so there is no way -- with just a where -- to find both students. That is where the aggregation comes in.
You need at least three Join conditions, and properly end the string Dave with quote :
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM SCHOOLS h
JOIN TOWNS t ON (t.id=h.town_id)
JOIN COUNTIES c ON (t.county_id=c.id)
WHERE EXISTS ( SELECT school_id
FROM STUDENTS s
WHERE s.first_name in ('Mick','Dave')
AND school_id = h.id
GROUP BY school_id
HAVING count(1)>1
);
SQL Fiddle Demo
You can use an analytic function in a sub-query to count the students who have the name Mick or Dave for each school_id (assuming that is your identifier for a school):
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM ( SELECT *
FROM (
SELECT d.*,
COUNT(
DISTINCT
CASE WHEN FIRST_NAME IN ( 'Mick', 'Dave' ) THEN FIRST_NAME END
) OVER( PARTITION BY school_id )
AS num_matched
FROM STUDENTS d
)
WHERE num_matched = 2
)
NATURAL JOIN SCHOOLS
NATURAL JOIN TOWNS
NATURAL JOIN COUNTIES;
SQLFiddle
You would also be better to use an INNER JOIN and explicitly specify the join condition rather than relying on NATURAL JOIN.
Related
Relational Schema:
Students (**sid**, name, age, major)
Courses (**cid**, name)
Enrollment (**sid**, **cid**, year, term, grade)
Write a SQL query that returns the name of the students who took all courses.I'm not sure how I capture the concept of 'ALL' in a SQL query.
EDIT:
I want to be able write it without aggregation as I want to use the same logic for writing the query in relational algebra as well.
Thanks for the help!
One way of writing such queries is to count the number of course and number of courses each student took, and compare them:
SELECT s.*
FROM students s
JOIN (SELECT sid, COUNT(DISTINCT cid) AS student_courses
FROM enrollment
GROUP BY sid) e ON s.sid = e.sid
JOIN (SELECT COUNT(*) AS cnt
FROM courses) c ON cnt = student_cursed
This gives course combinations that are possible but haven't been taken...
SELECT s.sid, c.cid FROM students CROSS JOIN courses
EXCEPT
SELECT sid, cid FROM enrollment
So, you can then do the same with the student list...
SELECT sid FROM students
EXCEPT
(
SELECT DISTINCT
sid
FROM
(
SELECT s.sid, c.cid FROM students CROSS JOIN courses
EXCEPT
SELECT sid, cid FROM enrollment
)
AS not_enrolled
)
AS slacker_students
I don't like it, but it avoids aggregation...
SELECT *
FROM Students
WHERE NOT EXISTS (
SELECT 1 FROM Courses
LEFT OUTER JOIN Enrollment ON Courses.cid = Enrollment.cid
AND Enrollment.sid = Students.sid
WHERE Enrollment.sid IS NULL
)
btw. names of tables should be in singular form, not plural
I have these tables with the following column names:
players:
id first_name last_name age position salary hire_date skills_data_id team_id
Skills:
id, dribbling, pace, passing, shooting, speed, strength
towns:
id, name, country_id
teams:
id name established fan_base stadium_id
On this base, I have found the players with the max speed in terms of towns where their team played.
At the same time, I have to skip players that played in team ‘Devify’.
At the moment I have tried with this code, but the final result is not correct.
select max(s.speed) as `max_speed`,tt.name as `town_name`
from skills_data as s
right join players as p on s.id = p.skills_data_id
inner join teams as t on p.team_id = t.id
inner join towns as tt on p.team_id = tt.id
where t.name not like 'Devify'
group by s.id
order by max(s.speed) desc, town_name;
The result should be something like that:
max_speed town_name
97 Smolensk
92 Bromma
92 Lühua
...
NULL Zavolzh’ye
My result is:
max_speed town_name
97 Montréal-Ouest
92 Dalubian
92 Samsan
Thank you in advance.
There is a problem in your group by clause: you should be grouping by town rather than by skill id.
I am also quite suspicious about the join condition that brings in the towns (see my comment under your question): but if you are getting results for your existing query, it must be right...
Other changes to your query:
changed your right join to an inner join
used more meaningful table aliases
used an inequality condition instead of not like to exclude the unwanted team
New query:
select
max(sk.speed) as max_speed,
to.name as `town_name`
from skills_data as sk
inner join players as pl on sk.id = pl.skills_data_id
inner join teams as te on pl.team_id = te.id and te.name <> 'Devify'
inner join towns as to on pl.team_id = to.id
group by to.id, to.name
order by max_speed desc, town_name;
I want to fetch all parents that have kids in a specific grade only in a school.
Below are trimmed down version of the tables.
TABLE students
id,
last_name,
grade_id,
school_id
TABLE parents_students
parent_id,
student_id
TABLE parents
id,
last_name,
school_id
I tried the below query but it doesn't really work as expected. It rather fetches all parents in a school disregarding the grade. Any help is appreciated. Thank you.
SELECT DISTINCT
p.id,
p.last_name,
p.school_id,
st.school_id,
st.grade_id,
FROM parents p
INNER JOIN students st ON st.school_id = p.school_id
WHERE st.grade_id = 118
AND st.school_id = 6
GROUP BY p.id,st.grade_id,st.school_id;
I would think:
select p.*
from parents p
where exists (select 1
from parents_students ps join
students s
on ps.student_id = s.id
where ps.parent_id = p.id and
s.grade_id = 118 and
s.school_id = 6
);
Your question says that you want information about the parents. If so, I don't see why you are including redundant information about the school and grade (it is redundant because the where clause specifies exactly what those values are).
I have four tables:
students
classes
teachers
teacher_assignments
classes and teachers has a many-to-many relationship and so teacher_assignments acts as the xref table (with fields teacher_id and class_id).
Each student in students has a class_id (many-to-one -- many students to one class).
I should also mention that teacher_assignments has an active column (BOOL) which indicates whether that assignment is currently active
What I want to do:
I want to retrieve the following:
class_name -- a concat of its level and sub_level, e.g. 3 and A
teacher_names -- the names of the teachers currently assigned to that class
student_count -- a count of the students in each class
At first, I tried retrieving just the class_name and teacher_names, like so:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names
FROM
teacher_assignments
LEFT JOIN teachers
ON teachers.id = teacher_assignments.teacher_id
AND teacher_assignments.active = TRUE
LEFT JOIN classes
ON classes.id = teacher_assignments.class_id
GROUP BY classes.id
This works fine and outputs:
class_name | teacher_names
--------------------------------------
1A | NULL
2A | John, Sam
3B | Sam, Sarah
(Class 1A has no teachers currently, and so the NULL is expected)
... BUT, now I have no idea how to work the student_count into this.
My question:
How exactly should the students table be joined with the others in the above query so I can produce a student_count column?
Use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COUNT(s.id) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Column aliases can be referenced in the GROUP BY when using MySQL, otherwise you'd have to duplicate the logic that produces the class_name column value. This is also the column to GROUP on, as GROUP_CONCAT and COUNT are aggregate functions.
To get zero as the count value, you might need to use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COALESCE(COUNT(s.id), 0) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Just thinking off the top of my head...
Join classes and students tables to get the student count...
Instead of doing a left join on classes in your above query, you will do a left join with the result from #1 (essentially an inner join between classes and students tables) that allows you to pull the student count.
I dont think I would use a join but instead would use an inline column select on student like this:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
( SELECT COUNT(*) FROM students WHERE students.class_id = classes.id ) AS student_count
FROM ...
I've got a student table and an enrollment table; a student could have multiple enrollment records that can be active or inactive.
I want to get a select that has a single student record and an indicator as to whether that student has active enrollments.
I thought about doing this in an inline UDF that uses the student ID in a join to the enrollment table, but I wonder if there's a better way to do it in a single select statement.
The UDF call might look something like:
Select Student_Name,Student_Email,isEnrolled(Student_ID) from Student
What might the alternative - with one SQL statement - look like?
select Student_Name,
Student_Email,
(select count(*)
from Enrollment e
where e.student_id = s.student_id
) Number_Of_Enrollments
from Student e
will get the number of enrollments, which should help.
Why not join to a secondary select? Unlike other solutions this isn't firing a subquery for every row returned, but gathers the enrollment data for everyone all at once. The syntax may not be quite correct, but you should get the idea.
SELECT
s.student_name,
s.student_email,
IsNull( e.enrollment_count, 0 )
FROM
Students s
LEFT OUTER JOIN (
SELECT
student_id,
count(*) as enrollment_count
FROM
enrollments
WHERE
active = 1
GROUP BY
student_id
) e
ON s.student_id = e.student_id
The select from enrollments could also be redone as a function which returns a table for you to join on.
CREATE FUNCTION getAllEnrollmentsGroupedByStudent()
RETURNS #enrollments TABLE
(
student_id int,
enrollment_count int
) AS BEGIN
INSERT INTO
#enrollments
(
student_id,
enrollment_count
) SELECT
student_id,
count(*) as enrollment_count
FROM
enrollments
WHERE
active = 1
GROUP BY
student_id
RETURN
END
SELECT
s.student_name,
s.student_email,
e.enrollment_count
FROM
Students s
JOIN
dbo.getAllEnrollmentsGroupedByStudent() e
ON s.student_id = e.student_id
Edit:
Renze de Waal corrected my bad SQL!
Try someting like this:
SELECT Student_Name, Student_Email, CAST((SELECT TOP 1 1 FROM Enrollments e WHERE e.student_id=s.student_id) as bit) as enrolled FROM Student s
I think you can also use the exists statement in the select but not positive
try to avoid using udfs or subqueries, they are performance killers. banjolity seems to havea good solution otherwise because it uses a derivd table instead of a UDF or subselect.
select students.name,
decode(count(1), 0, "no enrollments", "has enrollments")
from students, enrollments
where
students.id = enrollments.sutdent_id and
enrollments.is_active = 1 group by students.name
Of course, replace the decode with a function your database uses (or, a case statement).