SQL question: Show me students who have NOT taken a certain course? - sql

So I have these tables:
STUDENTS:
Student ID - First name - Last name - Email
COURSES:
Catalog ID - Course Name - Description
TERMS:
Term ID - Start Date - End Date
COURSEINSTANCES:
CourseInstance ID - Catalog ID - Term ID
STUDENTCOURSES:
StudentCourse ID - CourseInstance ID - Student ID - Date added to database
This makes it easy to see which students have taken which courses. I'm not sure how to go about finding out which students have NOT taken a particular course.
Doing something like this:
WHERE ((CourseInstances.CatalogLookup)<>504)
will just give me a list of courses taken by students that do not equal catalog number 504 like this:
Tara - 501
Tara - 502
Tara - 505
John - 503
So for example I've taken 504. Therefore I do not want me to show up on this list. The SQL above will just show all of my courses that are not 504, but it will not exclude me from the list.
Any ideas? Is this possible?

I prefer this syntax over outer joins, IMO it's easier to read:
select *
from STUDENTS
where StudentID not in
(
select StudentID
from STUDENTCOURSES s
inner join COURSEINSTANCES c on s.CourseInstanceID = c.CourseInstanceID
where c.CatalogID = 504
)
In the nested query, you select the StudentIDs of all students who HAVE taken course 504.
Then, you select all the students whose StudentIDs are not included in the nested query.
EDIT:
As ChrisJ already said, the c and the s are aliases for the table names.
Without them, the query would look like this:
select *
from STUDENTS
where StudentID not in
(
select StudentID
from STUDENTCOURSES
inner join COURSEINSTANCES on STUDENTCOURSES.CourseInstanceID = COURSEINSTANCES.CourseInstanceID
where CatalogID = 504
)
I always use aliases because:
a) I'm too lazy to type the table names more often than necessary.
b) In my opinion it's easier to read, especially when you join tables with long names.

Try something like this:
SELECT *
FROM Users
WHERE UserID NOT IN
( SELECT UserID
FROM
Users
INNER JOIN
ClassesTaken ON Users.UserID = ClassesTaken.UserID AND ClassesTaken.ClassNumber = 504)
Another way occurred to me the other day:
SELECT *
FROM
Users
LEFT OUTER JOIN ClassesTaken ON Users.UserID = ClassesTaken.UserID AND ClassesTaken.ClassNumber = 504
WHERE ClassesTaken.UserID IS NULL

You should read about outer joins.

SELECT * FROM students
WHERE studentId not in
(SELECT distinct studentID FROM studentCourses WHERE courseInstanceID = 504)

Three main ways in Access
NOT IN (Be careful to exclude any NULLs if there is any possibility of them appearing in the sub query)
OUTER JOIN and filter on NULL (may need DISTINCT added)
NOT EXISTS
Other RDBMSs also have EXCEPT or MINUS

homework? use set operators.
select all students MINUS select any student who has taken this course...

Related

Join a SQL resultset based on common email address

I have one table that contains both students and parents records. There is a common field (email address) in both parent and student records that match the records (parents and students have the same email address).
I want my query to find the common email addresses and return each
student record but it must also include a field (Mifare) from the
parents record.
So I have tried to join and where clauses but i am getting crazy results. I was also thinking of using the with clause.
Expected result is
The basic script is:
Select extid, first_name, last_name, commonemail, designation,mifare
from students
Please assist with a basic coding - no procedures etc...just simple help for a newbie!
Thank you all!
Here is the solution for your problem:
SELECT stud.ExtId,
stud.FirstName,
stud.LastName,
stud.CommonEmail,
stud.Designation,
stud.Mifare,
prnt.Mifare
FROM Students AS stud
INNER JOIN Students AS prnt
ON stud.CommonEmail = prnt.CommonEmail
AND stud.Designation = 'Student'
AND prnt.Designation = 'Parent'
You can also follow the link to the demo:
http://sqlfiddle.com/#!9/5b790b/1
Select s.*
p.mifare
from students s
left join students p on p.commonemail=s.commonemail and p.Designation='Parent'
Try using the following Sub query
Select *,
(Select Mifare from students b where b.Designation = 'Parent' and b.commonemail = a.commonemail) as ParentMifare
from students a
I am not sure you can try this way join both the tables with email select only the designation student only.
Select
st.extid,
st.first_name,
st.last_name,
st.commonemail,
st.designation,
st. mifare
pr.parentmifare
from students st inner join students pr on st.commonemail=pr.commonemail
where pr.designation='student' and st.designation='parent'

Compare 2 columns in 2 tables with DISTINCT value

I am now creating a reporting service with visual business intelligent.
i try to count how many users have been created under an org_id.
but the report consist of multiple org_id. and i have difficulties on counting how many has been created under that particular org_id.
TBL_USER
USER_ID
0001122
0001234
ABC9999
DEF4545
DEF7676
TBL_ORG
ORG_ID
000
ABC
DEF
EXPECTED OUTPUT
TBL_RESULT
USER_CREATED
000 - 2
ABC - 1
DEF - 2
in my understanding, i need nested SELECT, but so far i have come to nothing.
SELECT COUNT(TBL_USER.USER_ID) AS Expr1
FROM TBL_USER INNER JOIN TBL_ORG
WHERE TBL_USER.USER_ID LIKE 'TBL_ORG.ORG_ID%')
this is totally wrong. but i hope it might give us clue.
It looks like the USER_ID value is the concatenation of your ORG_ID and something to make it unique. I'm assuming this is from a COTS product and nothing a human would have built.
Your desire is to find out how many entries there are by department. In SQL, when you read the word by in a requirement, that implies grouping. The action you want to take is to get a count and the reserved word for that is COUNT. Unless you need something out of the TBL_ORG, I see no need to join to it
SELECT
LEFT(T.USER_ID, 3) AS USER_CREATED
, COUNT(1) AS GroupCount
FROM
TBL_USER AS T
GROUP BY
LEFT(T.USER_ID, 3)
Anything that isn't in an aggregate (COUNT, SUM, AVG, etc) must be in your GROUP BY.
SQLFiddle
I updated the fiddle to also show how you could link to TBL_ORG if you need an element from the row in that table.
-- Need to have the friendly name for an org
-- Now we need to do the join
SELECT
LEFT(T.USER_ID, 3) AS USER_CREATED
, O.SOMETHING_ELSE
, COUNT(1) AS GroupCount
FROM
TBL_USER AS T
-- inner join assumes there will always be a match
INNER JOIN
TBL_ORG AS O
-- Using a function on a column is a performance killer
ON O.ORG_ID = LEFT(T.USER_ID, 3)
GROUP BY
LEFT(T.USER_ID, 3)
, O.SOMETHING_ELSE;

Efficient way to select records missing in another table

I have 3 tables. Below is the structure:
student (id int, name varchar(20))
course (course_id int, subject varchar(10))
student_course (st_id int, course_id int) -> contains name of students who enrolled for a course
Now, I want to write a query to find out students who did not enroll for any course. As I could figure out there are multiple ways to fetching this information. Could you please let me know which one of these is the most efficient and also, why. Also, if there could be any other better way of executing same, please let me know.
db2 => select distinct name from student inner join student_course on id not in (select st_id from student_course)
db2 => select name from student minus (select name from student inner join student_course on id=st_id)
db2 => select name from student where id not in (select st_id from student_course)
Thanks in advance!!
The subqueries you use, whether it is not in, minus or whatever, are generally inefficient. Common way to do this is left join:
select name
from student
left join student_course on id = st_id
where st_id is NULL
Using join is "normal" and preffered solution.
The canonical (maybe even synoptic) idiom is (IMHO) to use NOT EXISTS :
SELECT *
FROM student st
WHERE NOT EXISTS (
SELECT *
FROM student_course
WHERE st.id = nx.st_id
);
Advantages:
NOT EXISTS(...) is very old, and most optimisers will know how to handle it
, thus it will probably be present on all platforms
the nx. correlation name is not leaked into the outer query: the select * in the outer query will only yield fields from the student table, and not the (null) rows from the student_course table, like in the LEFT JOIN ... WHERE ... IS NULL case. This is especially useful in queries with a large number of range table entries.
(NOT) IN is error prone (NULLs), and it might perform bad on some implementations (duplicates and NULLs have to be removed from the result of the uncorrelated subquery)
Using "not in" is generally slow. That makes your second query the most efficient. You probably don't need the brackets though.
Just as a comment: I would suggest to select student Id (which are unique) and not names.
As another query option you might want to join the two tables, group by student_id, count(course_id) having count(course_id) = 0.
Also, I agree that indexes will be more important.

SQL finding course name from course code

I have 2 tables, students and courses. In the students table each student has a course code and in the courses table I have course codes with their names.
How can I return the course name when running the query?
So for example if I run a basic SELECT * FROM Students it says "Jimmy | Computing" instead of "Jimmy | 12390814"
JOIN both tables. Try something like this,
SELECT a.*, b.courseName
FROM students a
INNER JOIN courses b
ON a.courseCode = b.courseCode
-- WHERE ....

SQL Database SELECT question

Need some help with an homework assignment on SQL
Problem
Find out who (first name and last name) has played the most games in the chess tournament with an ID = 41
Background information
I got a table called Games, which contains information...
game ID
tournament ID
start_time
end_time
white_pieces_player_id
black_pieces_player_id
white_result
black_result
...about all the separate chess games that have taken place in three different tournaments ....
(tournaments having ID's of 41,42 and 47)
...and the first and last names of the players are stored in a table called People....
person ID (same ID which comes up in the table 'Games' as white_pieces_player_id and
black_pieces_player_id)
first_name
last_name
...how to make a SELECT statement in SQL that would give me the answer?
sounds like you need to limit by tournamentID in your where clause, join with the people table on white_pieces_player_id and black_pieces_player_id, and use the max function on the count of white_result = win union black_result = win.
interesting problem.
what do you have so far?
hmm... responding to your comment
SELECT isik.eesnimi
FROM partii JOIN isik ON partii.valge=isik.id
WHERE turniir='41'
group by isik.eesnimi
having count(*)>4
consider using the max() function instead of the having count(*)> number
you can add the last name to the select clause if you also add it to the group by clause
sry, I only speak American. What language is this code in?
I would aggregate a join to that table to a derived table like this:
SELECT a.last_name, a.first_name, CNT(b.gamecount) totalcount
FROM players a
JOIN (select cnt(*) gamecount, a.playerid
FROM games
WHERE a.tournamentid = 47
AND (white_player_id = a.playerid OR black_player_id = a.playerid)
GROUP BY playerid
) b
ON b.playerid = a.playerid
GROUP BY last_name, first_name
ORDER BY totalcount
something like this so that you are getting both counts for their black/white play and then joining and aggregating on that.
Then, if you only want the top one, just select the TOP 1