select ... not exists not exists query - sql

select r.index, sum(c.points)
from records r join exams e2 on r.index = e2.index
join courses c on c.id_course = e2.id_course
where not exists ( select *
from required_courses rs
where rs.id_studie = r.id_studie
and not exists (select *
from exams e
where e.id_course = rs.id_course
and r.index = e.index
and score>5))
and score>5
group by index;
I have that query. I know what it does, but don't know how.
I have relativly big database with 16 tables, but only use 4 in this query.
Used tables are:
RECORDS (of students) [index (pr. key), name, surname, ..., id_studie(1)(for. key)]
EXAMS [index(of students) (p.k.), id_course(p.k.),..., score(2), application_status]
COURSES [id_course(p.k.), ..., points]
REQUIRED_COURSES [id_studie (f.k.), id_course(f.k), ...]
(1) I don't know better word in English. When on some faculty there are Informatics, Math, Physics, etc. classes. Informatics is one studie.
(2) Scores go from 5 to 10. 10 is best. At least 6 is required for passing.
Query:
- Find all students that passed all required exams on studie thay studie. Print indexes and number of points.
My question: Can sameone explain how does this work in simple words?
I don't understand not exists & not exists part.
Sorry for my bad english.

Always analyze the query from the inside out.
Innermost query:
Select the exams with score higher than 5
Middle one:
Select the required courses that don't have an exam with score higher than 5
Outer one:
Select students that don't have a required course that doesn't have the exam with score higher than 5

The example is a double-nested NOT EXISTS query.
If a subquery returns any rows at all, EXISTS subquery is TRUE, and NOT EXISTS subquery is FALSE.
That is, it has a NOT EXISTS clause within a NOT EXISTS clause.
It is easier to say that a nested NOT EXISTS answers the question “is x TRUE for all y?”
--Part 3
SELECT r.index,
SUM(c.points)
FROM records r
join exams e2
ON r.index = e2.index
join courses c
ON c.id_course = e2.id_course
WHERE NOT EXISTS (
--Part 2 starts
SELECT *
FROM required_courses rs
WHERE rs.id_studie = r.id_studie
AND NOT EXISTS (
--Part 1 starts
SELECT *
FROM exams e
WHERE e.id_course = rs.id_course
AND r.index = e.index
AND score > 5
--Part 1 ends
)
--Part 2 ends
)
AND score > 5
GROUP BY index;
--Part 3 ends
Part 1: Fetch all records of students from EXAMS table who scored more than 5. You should get all pass mark students for all courses here
Part 2: Join Part1 results with REQUIRED_COURSES on student id and fetch all the required courses where the student has not cleared the exams (where student does not have a score more than 5). You should have all the student's courses name where they are not successful
Part 3: Join Part 2 results with RECORDS table on index and also COURSES table on course id to fetch the index and sum of points. You can see this part into two pieces. First is the normal JOIN with two tables and then the NOT EXISTS with the part 2. When you apply a NOT EXISTS on part 2, you are going to get all successful student ids which would provide you the successful entries by adding another SCORE > 5 condition at the end.

Related

How to Limit Results Per Match on a Left Join - SQL Server

I have a table with student info [STU] and a table with parent info [PAR]. I want to return an email address for each student, but just one. So I run this query:
SELECT [STU].[ID], [PAR].[EM]
FROM (SELECT [STU].* FROM DB1.STU)
STU LEFT JOIN (SELECT [PAR].* FROM DB1.PAR) PAR ON [STU].[ID] = [PAR].[ID]
This gives me the below table:
Student ID ParentEmail
1 jim#email.com
1 sarah#email.com
2 paul#email.com
2 tim#email.com
3 bill#email.com
3 frank#email.com
3 joyce#email.com
4 greg#email.com
5 tony#email.com
5 sam#email.com
Each student has multiple parent emails, but I only want one. In other words, I want the output to look like this:
Student ID ParentEmail
1 jim#email.com
2 paul#email.com
3 frank#email.com
4 greg#email.com
5 sam#email.com
I've tried so many things. I've tried using GROUP BY and MIN/MAX and I've tried complex CASE statements, and I've tried COALESCE but I just can't seem to figure it out.
I think OUTER APPLY is the simplest method:
SELECT [STU].[ID], [PAR].[EM]
FROM DB1.STU OUTER APPLY
(SELECT TOP (1) [PAR].*
FROM DB1.PAR
WHERE [STU].[ID] = [PAR].[ID]
) PAR;
Normally, there would be an ORDER BY in the subquery, to give you control over which email you want -- the longest, shortest, oldest, or whatever. Without an ORDER BY it returns just one email, which is what you are asking for.
If you just want one column from the parent table, a simple approach is a correlated subquery:
select
s.id student_id,
(select max(p.em) from db1.par p where p.id = s.id) parent_email
from db1.stu s
This gives you the greatest parent email per student.

Get courses chosen by all students

Now there are two tables student(sid, name) and course(cid, name), which relation is many-to-many. So there is another table student_course(sid, cid) that stores the information about what courses have been chosen by whom.
How to write the sql that can get the courses that have been chosen by all the students?
Standard solution: use the NOT EXISTS ( ... NOT EXISTS (...)) construct:
find all courses in which all students take part
==>> there must not exist a student that does not take part in this course
SELECT * FROM course c
WHERE NOT EXISTS (
SELECT * from student s
WHERE NOT EXISTS (
SELECT * from student_course cs
WHERE cs.sid = s.sid
AND cs.cid = c.cid
)
)
;
This query is often faster (given appropiate indexes) than the count() == count() variant. The reason for this: you do not have to count all the (distinct) records; once you found one student that does not take this course you can omit this course from your list of suspects. Also: ANTI-joins often can make use of indexes [so can a count(), but that still has to count all the (distinct) keyvalues in the index]
Select c.cid, c.name
From course c where
(select count(1) from student) = (select count(1) from student_course sc where sc.cid = c.cid);
See SQL Fiddle
It finds all courses where the count of entries for that course in the student_course table matches the number of students
CID NAME
1 Test Course1
4 Test Course4

How to fetch the non matching rows in Oracle

Can anyone help me fetch the non matching rows from two tables in Oracle?
Table: Names
Class_id Stud_name
S001 JAMES
S001 PETER
S002 MARK
Table: Course
Course_id Stud_name
S001 JAMES
S001 KEITH
S002 MARK
Output
I need the rows to display as
CLASS ID STUD_NAME_FROM_NAME_TABLE STUD_NAME_FROM_COURSE_TABLE
---------------------------------------------------------------------
S001 PETER KEITH
I have used Oracle joins to fetch the non matching names:
SELECT *
FROM Names, Course
WHERE Names.Class_id=Course.Course_id
AND Names.Stud_name<>Course.Stud_name
This query is returning duplicate rows.
If you insist on Join you can use this one:
SELECT *
FROM Names
FULL OUTER JOIN Course ON Names.Class_id=Course.Course_id
AND Names.Stud_name = Course.Stud_name
WHERE Names.Stud_name IS NULL or Course.Stud_name IS NULL
Fetches unmatched rows in Names table
SELECT * FROM Names
WHERE
NOT EXISTS
(SELECT 'x' from Course
WHERE
Names.Class_id = Course.Course_id AND
Names.Stud_name = Course.Stud_name)
Fetches unmatched rows in Names and Course too!
SELECT Names.Class_id,Names.Stud_name,C1.Stud_name
FROM Names , Course C1
WHERE Names.Class_id = C1.Course_id AND
NOT EXISTS
(SELECT 'x' from Course C2
WHERE
Names.Class_id = C2.Course_id AND
Names.Stud_name = C2.Stud_name);
When you ask for unmatching rows I assume that you want rows that exist in names but not in course.
If this is the case you're probably after
select * from names
where (class_id, stud_name ) not in
(select course_id, stud_name from course);
Your query returned duplicate rows beacuse for each row in names it selected all rows in course that satisfied the where condition.
So, for the row S001, PETER in names it faound that S001, JAMES and S001, KEITH matched that condition, thus, that row was "returned" twice.
EDIT Since it is not clear if stud_name is a primary key, or unique (and on second sight I think it's not), you'd probably want a
select * from names
where not exists (
select 1 from course where
names.class_id = course.course_id and
names.stud_name <> course.stud_name
)
Edit II if you insist on using a join (as per your comment) you might want to try a
select distinct names.* from...
Hope it helps you
with not_in_class as
(select a.*
from Names a
where not exists ( select 'x'
from course b
where b.Course_id = a.class_id
and a.Stud_name = b.Stud_name)),
not_in_course as
(select b.*
from course b
where not exists ( select 'x'
from Names a
where b.Course_id = a.class_id
and a.Stud_name = b.Stud_name))
select x.class_id,
x.Stud_name NOT_IN_CLASS,
y.stud_name NOT_IN_COURSE
from not_in_class x, not_in_course y
where x.class_id = y.course_id
Output
| CLASS_ID | NOT_IN_CLASS | NOT_IN_COURSE |
|----------|--------------|---------------|
| S001 | PETER | KEITH |
Only problem is that if multiple mismatches are there in both the tables for a given id, it works for single mismatch for a particular id. You need to rework if multiple mismatches are there for the same id.
Well, I am not sure if I understand correctly what you are asking. I think you want a list of all IDs where the student list in class table and course table differs. Then you want to show the id and the students that are in class but not in course and the students that are in course but not in class.
To do so you would full outer join the tables. That gives you students that are both in class and course, students that are in class and not in course, and students that are in course and not in class. Filter your results where either class_id or course_id is null then to get the students missing in course or class. At last group by id and list the students.
select coalesce(class.class_id, course.course_id) as id
, listagg(class.stud_name, ',') within group (order by class.stud_name) as missing_in_course
, listagg(course.stud_name, ',') within group (order by course.stud_name) as missing_in_class
from class
full outer join course
on (class.class_id = course.course_id and class.stud_name = course.stud_name)
where class.class_id is null or course.course_id is null
group by coalesce(class.class_id, course.course_id);
Here is the SQL fiddle showing how it works: http://sqlfiddle.com/#!4/8aaaa/2
EDIT: In Oracle 9i there is no listagg. You can use the inofficial function wm_concat instead:
select coalesce(class.class_id, course.course_id) as id
, wm_concat(class.stud_name) as missing_in_course
, wm_concat(course.stud_name) as missing_in_class
from class
full outer join course
on (class.class_id = course.course_id and class.stud_name = course.stud_name)
where class.class_id is null or course.course_id is null
group by coalesce(class.class_id, course.course_id);

Query - limit the number of classes a teacher can teach

I am working on my senior Project. A mini version of an online registration system and website. In the assumptions we want to limit the number of classes a teacher can teach in a semester to 3.
TblEmployee contains: emp_ID (key), can_Teach (first 4 letters of topic).
TblCourse contains: course_ID (Key), course_Type (first 4 letters of topic).
TblClass contains: ClassID (Key), emp_ID (FK), course_ID (FK) semester (period of time)
So far this works fine in selecting the proper teacher that teaches that class:
SELECT *
FROM tblEmployee, tblCourse
WHERE tblEmployee.canTeach = tblCourse.course_Type
AND tblClass.course_ID = ?
However I want to add to it that from tblClass if emp_ID (COUNT) < 3 to include those and exclude those greater than or equal to 3 (i.e. they will not show up in the drop down list.
I tried this but it did not work.
SELECT *
FROM tblEmployee, tblCourse
WHERE tblEmployee.canTeach = tblCourse.course_Type
AND tblClass.course_ID = ?
INNER JOIN (SELECT
tblEmployee.emp_ID, tblClass.emp_ID
FROM
tblEmployee, tblClass
GROUP BY
tblClass.emp_ID
HAVING
count(*) < 3 AND tblClass.emp_ID = tblEmployee.emp_ID)
and semester = ?
Any help would be appreciated.
I would start on the sub query, slim it down, and make your syntax consistent.
SELECT
tblClass.emp_ID
FROM
tblClass
where semester = ?
GROUP BY
tblClass.emp_ID
HAVING count(tblClass.emp_ID) < 3
As I understand it, that is IDs which are in the table less than 3 times (teachers that are teaching less than 3 courses).
For your outer query, you want employees that meets its criteria but are also in the inner set. Again, make your syntax consistent. Then try an IN.
SELECT *
FROM tblEmployee inner join tblCourse
on tblEmployee.canTeach = tblCourse.course_Type
where tblClass.course_ID = ?
and tblEmployee.emp_id
in
{
SELECT
tblClass.emp_ID
FROM
tblClass
where semester = ?
GROUP BY
tblClass.emp_ID
HAVING count(tblClass.emp_ID) < 3
}
See what that does.

cant understand how this view in SQL works

I am having a hard time understand how the create view, TRANSCRIPTVIEW, manages to set the grade of 0 for those who did not take a course. An explanation would help, the solution and question is below. Thanks.
Student(Id,Name)
Transcript(StudId,CourseName,Semester,Grade)
Formulate the following query in SQL:
Create a list of all students (Id, Name) and, for each student, list the average grade for the courses taken in the S2002 semester.
Note that there can be students who did not take any courses in S2002. For these, the average grade should be listed as 0.
Solution:
We first create a view which augments TRANSCRIPT with rows that enroll every student into a NULL course with the grade of 0. Therefore, students who did not take anything in semester ’S2002’ will have the average grade of 0 for that semester.
Below is what confuses me, how does this work and why does it work?
CREATE VIEW TRANSCRIPTVIEW AS (
( SELECT * FROM Transcipt)
UNION
(
SELECT S.Id,NULL,’S2002’,0
FROM Student S)
WHERE S.Id NOT IN (
SELECT T.StudId
FROM Transcript T
WHERE T.Semester = ’S2002’) )
)
Remaining solution:
SELECT S.Id, S.Name, AVG(T.Grade)
FROM Student S, TRANSCRIPTVIEW T
WHERE S.Id = T.StudId AND T.Semester = ’S2002’ GROUP BY S.Id
how the create view, TRANSCRIPTVIEW, manages to set the grade of 0 for those who did not take a course
The set of students who did not take a course in semester S2002 have no record in the transcript table for that semester. Those who did take a course in that semester do have a record in the table for that semester. The query supplies values NULL, 'S2002',0 for students if they are not in the Transcript table for semester S2002:
SELECT S.Id,NULL,’S2002’,0 FROM Student S) -- this parenthesis is wrong
-- this following where conditions looks for students NOT IN the 2002 subset:
WHERE S.Id NOT IN
-- this next part gets a list of studentids for semester 2002
(
SELECT T.StudId FROM Transcript T
WHERE T.Semester = ’S2002’
)
The solution in your qusetion is ridiculous. The better solution is:
SELECT S.Id, S.Name, AVG(case when T.Semester = ’S2002’ then T.Grade end) as AvgS2002Grade
FROM Student S left outer join
TRANSCRIPTVIEW T
on S.Id = T.StudId AND T.Semester = ’S2002’
GROUP BY S.Id
The query in your question is overly complicated. It is using a union (which should really be union all for performance reasons) to be sure all students are included. Gosh, this is what left outer join is for. It is doing filtering in the where clause, when a conditional aggregation is more suitable. It uses archaic join syntax, instead of the ANSI standard.
I hope you are not learning SQL with those shortcomings.