SQL query, NOT EXISTS - sql

Hi I am evaluating a SQL query with the following schema:
Student(sid, name, age, gender, dept, GPA)
Faculty(fid, name, age, office, dept)
Course(cid, name, description)
Teach(fid, cid, term)
Enrollment(sid, cid, term, grade, final grade)
The question to base query on is:
Find the students who took at least one course with Tom
The query looks like:
SELECT *
FROM Student S1
WHERE NOT EXISTS (SELECT E1.cid
FROM Enrolment E1, Student S2
WHERE E1.sid = S2.sid
AND S2.name = 'Tom'
MINUS
SELECT E2.Cid
FROM E2 Enrollment
WHERE E2.sid = S1.sid)
This query is not making sense to me because to me it seems like the NOT EXISTS condition would only select a student when the subquery is empty, and this would only occur in the case when student s1 has taken all the same courses as Tom. However the question is to find the students who took at least one course, not all courses as Tom. Maybe I am interpreting wrong but I could use some clarification.

Try this
DECLARE #TomID nvarchar(MAX)
set #TomID = (SELECT TOP 1 ID from Students where Name = 'Tom')
select distinct E1.SID from Enrollment E1
where E1.SID != #TomID AND E1.CID in
(select distinct E.CID from Enrollment E
where E.SID=#TomID)

Related

'ALL' concept in SQL queries

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

SQL Select with multiple conditions

I have 3 tables:
The question is "List every course number in which both ‘John Smith’ and ‘Kevin Miller’ are enrolled."
My query:
SELECT DISTINCT CourseNumber
FROM Enrollment
INNER JOIN Student ON Student.SSN = Enrollment.SSN
WHERE Student.Name = 'John Smith'
AND Student.Name = 'Kevin Miller'
but it didn't return anything.
If I change the operator "AND" to "OR", it's going to show 2 courses number which is the wrong answer.
Can anybody help me please? Thank you
You need some kind of aggregation logic here, because the assertion on both students inherently involves more than one record. So, a WHERE clause by itself won't work. Here is one common way to approach this:
SELECT e.CourseNumber
FROM Enrollment e
INNER JOIN Student s ON s.SSN = e.SSN
WHERE s.Name = IN ('John Smith', 'Kevin Miller')
GROUP BY e.CourseNumber
HAVING MIN(s.Name) <> MAX(s.Name);
I prefer the above approach, but you could also use exists logic here:
SELECT DISTINCT e.CourseNumber
FROM Enrollment e
INNER JOIN Student s ON s.SSN = e.SSN
WHERE
e.Name = 'John Smith' AND
EXISTS (SELECT 1 FROM Enrollment e1
INNER JOIN Student s1 ON s1.SSN = e1.SSN
WHERE e1.CourseNumber = e.CourseNumber AND
s1.Name = 'Kevin Miller');
Just to show an alternative to Tim's answer: A simple approach is INTERSECT.
SELECT coursenumber FROM enrollment WHERE ssn = (SELECT ssn FROM Student WHERE name = 'John Smith')
INTERSECT
SELECT coursenumber FROM enrollment WHERE ssn = (SELECT ssn FROM Student WHERE name = 'Kevin Miller')
There are some DBMS that don't support INTERSECT, though.

SQL Select entries where none of the entities have a value in a particular column

I have a table of data which has students and their subject results in it. The students will appear multiple times, once for each subject they have a result for.
**tableID,studentID,lastName,firstName,subject,grade**
1,1a,Student1,Name1,English,A
2,1a,Student1,Name1,Maths,A
3,1a,Student1,Name1,Science,A
4,2a,Student2,Name2,English,A
5,2a,Student2,Name2,Maths,B
6,2a,Student2,Name2,Science,A
7,3a,Student3,Name3,English,A
8,3a,Student3,Name3,Maths,A
Using Microsoft Access SQL, how can I select only the students who have received an A for all of their subjects? E.g. In the above table, I only want to select all instances of Student1 and Student3, I don't want Student2 as they have not received all A's.
Get all students with grade A except students with any other grade
SELECT
studentID,lastName,firstName
FROM
(SELECT
studentID,lastName,firstName
FROM
result
WHERE
grade = 'A'
GROUP BY
studentID,lastName,firstName) GradeA
LEFT OUTER JOIN
(SELECT
studentID,lastName,firstName
FROM
result
WHERE
grade <> 'A'
GROUP BY
studentID,lastName,firstName) GradeOther
ON GradeA.studentId = GradeOther.StudentID AND GradeA.LAstName = GradeOther.LastName AND GradeA.FirstName = GradeOther.FirstName
WHERE
GradeOther.StudentID IS NULL
One way is using GROUP BY and HAVING:
select StudentId
from t
group by StudentId
having max(grade) = min(grade) and max(grade) = 'A';
I was able to get the results I want by using a sub-query:
SELECT studentID, lastName, firstName
FROM table
WHERE grade = "A"
AND studentID NOT IN (SELECT studentID FROM table WHERE grade <> "A" GROUP BY studentID)
GROUP BY studentID, lastName, firstName
This seems to exclude all students who received a result other than an A.

SQL Query: Retrieve list which matches criteria

Sorry, I couldn't think of a better heading (or anything that makes sense).
I have been trying to write a SQL query where I can retrieve the names of student who have the same level values as student Jaci Walker.
The format of the table is:
STUDENT(id, Lname, Fname, Level, Sex, DOB, Street, Suburb, City, Postcode, State)
So I know the Lname (Walker) and Fname (Jaci) and I need to find the Level of Jaci Walker and then output a list of names with the same Level.
--Find Level of Jaci Walker
SELECT S.Fname, S.Name, S.Level
FROM Student S
WHERE S.Fname="Jaci" AND S.Lname="Walker"
GROUP BY S.Fname, S.Lname, S.Level;
I have figured out how to retrieve the Level of Jaci Walker, but don't know how to apply that to another query.
Thankyou to everyone for your help,
I'm just stuck on one little bit when adding the rest of the query into it.
https://www.dropbox.com/s/3ws93pp1vk40awg/img.jpg
SELECT S.Fname, S.LName
FROM Student S, Enrollment E, CourseSection CS, Location L
WHERE S.S_id = E.S_id
AND E.C_SE_ID = CS.C_SE_id
AND L.Loc_id = CS.Loc_ID
AND S.S_Level = (SELECT S.S_Level FROM Student S WHERE S.S_Fname = "Jaci" AND S.S_Lname = "Walker")
AND CS.C_SE_id = (SELECT CS.C_SE_id FROM CourseSection CS WHERE ?)
AND L.Loc_id = (SELECT L.Blodg_code FROM Location L WHERE L.Blodg_code = "BG");
try this :
SELECT S.Fname, S.Name, S.Level
FROM Student S
WHERE S.Level =
(SELECT Level
FROM Student
WHERE Fname="Jaci" AND Lname="Walker"
)
but you got to be sure to have only 1 student called Jaci Walker ...
You can re-use your query as a subquery to find other entries with the same Level.
SELECT Fname, Name
FROM Student
WHERE Level = (
SELECT Level FROM Student S WHERE S.Fname="Jaci" AND S.Lname="Walker")
You don't need to group your result.
Try this
Select Fname,Lname from Student
where Level=(Select Level
from Student
where Fname='Jaci' AND Lname='Walker' );
Try this:
SELECT S.Fname, S.Name, S.Level FROM Student s
WHERE Level =
(SELECT TOP 1 Level FROM Student WHERE Fname = "Jaci" and Lname = "Walker")
If you don't use TOP 1, this query will fail if you have more than one "Jaci Walker" in your data.
I don't think you need group by and all for same...
Simply,
SELECT S.Fname, S.Name, S.Level FROM Student S WHERE S.LEVEL LIKE (SELECT LEVEL FROM STUDENT WHERE Fname="Jaci" AND Lname="Walker");
Are you looking for same?

Getting single records back from joined tables that may produce multiple records

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