Row that should have a 0 count not showing - sql

I'm working through a problem, working with SQL Oracle. I'm getting the right results beside a row that should be showing up that has a count of 0. The question is
Question:
For each section of the Project Management course, list the section ID, location and number of students enrolled. Sort by section ID.
My Code:
SELECT s.Section_Id, s.Location, COUNT(*) AS Enrolled
FROM Course c, Section s, Enrollment e
WHERE c.Course_No = s.Course_No
AND s.Section_Id = e.Section_Id
AND c.Description = 'Project Management'
GROUP BY c.Course_No, s.Location, s.Section_Id
ORDER BY s.Section_Id;
My Results:
SECTION_ID LOCATION ENROLLED
---------- ------------------- ----------
48 L211 4
119 L211 3
120 L214 2
Expected Results:
SECTION_ID LOCATION ENROLLED
---------- ------------------- ----------
48 L211 4
119 L211 3
120 L214 2
121 L507 0
So as you can see I'm missing the row with 0 enrolled on my results and can't seem to get that row to appear. Also you will notice that it is a section id and location that go with it for that project management class but it won't appear. I'm not sure what I'm doing wrong.
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer

How about joining to the tables instead of using the WHERE clause:
SELECT s.Section_Id,
s.Location,
COUNT(e.Section_Id) AS Enrolled
FROM course c
LEFT JOIN section s
ON c.Course_No = s.Course_No
LEFT JOIN enrollment e
ON s.Section_Id = e.Section_Id
WHERE c.Description = 'Project Management'
GROUP BY c.Course_No,
s.Location,
s.Section_Id
ORDER BY s.Section_Id;

#OmniSquad you could use SQL provided by Linger to create LEFT OUTER JOIN and select records where is no enrolment and changes statement COUNT(e.Section_Id) AS Enrolled.

You need to outer join to course and enrollment otherwise you won't see sections that don't have any courses/enrollments. You can either use the ANSI syntax ...LEFT JOIN... etc or the old Oracle syntax using (+) against the columns of the deficient table:-
SELECT s.Section_Id, s.Location, COUNT(*) AS Enrolled
FROM Course c, Section s, Enrollment e
WHERE c.Course_No (+) = s.Course_No
AND s.Section_Id = e.Section_Id (+)
AND c.Description (+) = 'Project Management'
GROUP BY c.Course_No, s.Location, s.Section_Id
ORDER BY s.Section_Id;
These days I would use the ANSI syntax...

Related

SQL - Assess minimum value across multiple rows, then filter to show list of unique rows?

My end goal is to create a list of honor roll students. Each student has multiple rows, one for each grade. I want to say, look at their grades across these rows; only show 1 student name if none of their grades are <80%.
I've started just with this, but I'm stuck, I don't know how to assess across the multiple rows as a criterion for selecting a unique list.
SELECT students.first_name, students.last_name, storedgrades.storecode, storedgrades.percent,storedgrades.course_name
FROM storedgrades join
students
on students.ID = storedgrades.StudentID
where students.enroll_status=0 AND
storedgrades.termid>2799 AND
storedgrades.storecode = 'Q4'
Example of grades table:
BOB A 95
BOB D 65
ANDREA B 85
ANDREA A 95
EXAMPLE RESULT:
ANDREA
Use aggregation. I think this is what you want:
select s.id, s.first_name, s.last_name
from students s join
storedgrades sg
on s.ID = sg.StudentID
where s.enroll_status = 0 and
sg.termid > 2799 AND
sg.storecode = 'Q4'
group by s.id, s.first_name, s.last_name
having count(*) = 5 and -- you want all five courses
min(scorecode) >= 80;
Select a distinct list of student names, then compare with a NOT EXISTS to the grades table where the grade is less than 80%
SELECT distinct students.ID, students.first_name, students.last_name
FROM storedgrades s join
students
on students.ID = storedgrades.StudentID
where students.enroll_status=0 AND
storedgrades.termid>2799 AND
storedgrades.storecode = 'Q4' and not exists
(select 'x' from storedgrades s2 where s2.students.StudentID = s.StudentID and s2.first_name = s.first_name and
s.last_name = s2.last_name and s2.percent < 80)
EDIT: added in studentID to the join

Oracle SQL: Nested sub-queries, outer are the same, inner differs

I'm in a situation where I have several sub-queries, joined by UNION, each nested with an inner sub-query. The outer sub-queries are fully identical to each other, while the inner queries differ and are unique.
Reuse of the entire outer sub-queries is cumbersome for reading and making changes, and it would be greatly beneficial if they could be defined once and reused. So this is a question about creating reusable SQL-queries, but with distinct inner sub-queries that are passed on as arguments.
For my examples, I will present a simplified case that has the same issue as my real code.
We're using Oracle SQL for our project.
Say that we have a database for a school or university, with the tables PERSON, STUDENT, GRADE and COURSE, and all are connected by FK-relationships.
I need to run a query which gathers a list, counting up the number of people once for every criteria:
Number of students whose last name begin on the letter 'E'
Number of students older than 20 years
Number of people (including but not limited to students) who are female
Number of students who passed the course "Intermediate Norwegian" with grade B or higher
The expected outcome:
| Description | Number_of_students
1 | Last names beginning with letter "E" | 32
2 | Older than 20 years | 154
3 | All female persons | 356
4 | Passed "Intermediate Norwegian" with grade >= B | 12
Below is a query which should satisfy what I need.
It consists of several sub-queries joined by UNION, and all have their own distinct inner query.
The code is far from brilliant, but that is beside the point. The real question is about drastically improving readability. The outer sub-queries have the same structure that could be re-used, but the inner ones are different.
SELECT * FROM
-- 1st entry: Number of students on the last name 'E'
(SELECT 'Last names beginning with letter "E"' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1
WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'
)
)
UNION
-- 2nd entry: Number of students older than 20 years
(SELECT 'Older than 20 years' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
SELECT DISTINCT p2.ID FROM PERSON p2, STUDENT s2
WHERE p2.ID = s2.PERSON_ID AND p2.AGE > 20
)
)
UNION
-- 3rd entry: Number of female persons, including but not limited to students
(SELECT 'All female persons' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
SELECT DISTINCT p3.ID FROM PERSON p3 WHERE p3.GENDER = 'Female'
)
)
UNION
-- 4th entry: Students who passed the course "Intermediate Norwegian" with grade B or higher
(SELECT 'Passed "Intermediate Norwegian" with grade >= B' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
SELECT DISTINCT p4.ID FROM PERSON p4, STUDENT s4, GRADE g4 AND COURSE c4
WHERE p4.ID = s4.PERSON_ID
AND s4.ID = g4.STUDENT_ID
AND g4.COURSE_ID = c4.ID
AND (g4.GRADE = 'A' OR g4.GRADE = 'B')
AND c4.COURSE_NAME = 'Intermediate Norwegian'
)
)
Like I said, the code is far from brilliant. I won't be surprised if some of you cringed at what you just read.
For instance, the entire fourth one could easily be replaced by a query where you replace the entire inner query with g.GRADE = 'A' OR 'B' and c.COURSE_NAME = 'Intermediate Norwegian'.
But like I said, that is not the point here.
Every outer sub-query has the same structure:
(SELECT 'Passed "Intermediate Norwegian" with grade >= B' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
-- Inner Sub-query here
)
While every Sub-query has an inner one that differs from each other. Like the 1st and the 3rd one:
SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1 WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'
and
SELECT DISTINCT p3.ID FROM PERSON p3 WHERE p3.GENDER = 'Female'
What I need:
The real code I'm working with is far more complex, but has the same following issues as presented in the example above.
The result must be a list with several numbers, each categorized by their own distinct criteria (preferably described in the first column).
It consists of several sub-queries, joined by UNION
Each of these sub-queries are completely identical, with the exception of an inner sub-query that completely unique, and different from the others.
The resulting code is a huge beast, but could in theory be made far more readable if the outer code had been written only once, and reused with different inner code passed on as arguments.
I have recently come across the WITH-clause in Oracle SQL.
Something similar to this following change would be very beneficial:
WITH outer_sub_query AS (
SELECT 'DESCRIPTION HERE' AS Description, count(*) AS Number_of_students FROM
FROM PERSON p, STUDENT s, GRADE g, COURSE c
WHERE p.ID = s.PERSON_ID
AND s.ID = g.STUDENT_ID
AND g.COURSE_ID = c.ID
-- ... other complex code here
AND p.ID IN(
-- INSERT INNER SUB-QUERY HERE
)
)
SELECT * FROM (
outer_sub_query -- Last Names beginning with letter 'E'
UNION
outer_sub_query -- Age > 20
UNION
outer_sub_query -- All female
UNION
outer_sub_query -- Passed that course with grade >= B
)
Unfortunately, my needs are not yet satisfied. I still need to pass on the inner sub-queries, as well as descriptions. Something similar to this:
SELECT * FROM (
outer_sub_query(
'Last names beginning with letter "E",'
SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1
WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'
)
UNION
outer_sub_query(
'Older than 20 years.'
SELECT DISTINCT p2.ID FROM PERSON p2, STUDENT s2
WHERE p2.ID = s2.PERSON_ID AND p2.AGE > 20
)
UNION
outer_sub_query(
'All female persons'
SELECT DISTINCT p3.ID FROM PERSON p3 WHERE p3.GENDER = 'Female'
)
UNION
outer_sub_query(
'Passed "Intermediate Norwegian" with grade >= B'
SELECT DISTINCT p4.ID FROM PERSON p4, STUDENT s4, GRADE g4 AND COURSE c4
WHERE p4.ID = s4.PERSON_ID
AND s4.ID = g4.STUDENT_ID
AND g4.COURSE_ID = c4.ID
AND (g4.GRADE = 'A' OR g4.GRADE = 'B')
AND c4.COURSE_NAME = 'Intermediate Norwegian'
)
)
The questions:
Now, defining a FUNCTION easily comes to mind. But it still brings me some questions:
It first glance, it seems that WITH-clause does not take in parameters that can be passed on. Is there any other pre-existing clauses or functions in SQL or Oracle SQL that handles this?
Is it possible to extract the inner sub-query out from the outer one, and still achieve the same result? (Remember: No changes in the outer sub-query itself).
If I am to define a FUNCTION that handles this, is it possible to pass on pure SQL-codes like I have done above?
Is there any other smart solutions that I am missing?
Thank you for your advice(s).
A common table expression (such as you already suggested) seems a likely approach for reducing code duplication in your case, but you're trying to get it to do too much for you. CTEs cannot be parameterized in the way you hope; if they were, then a use such as you envision would no longer have them in common.
Yes, you could write a table-valued function, but that seems way overkill, and it could well be difficult for the query planner to analyze. Here's about as far as you can go with a CTE:
WITH student_grades AS (
SELECT
p.id AS id,
p.lastname AS lastname,
p.age AS age,
p.gender AS gender,
c.course_name AS course_name,
g.grade AS grade
FROM
-- You really, really should use ANSI JOIN syntax:
PERSON p
JOIN STUDENT s ON p.ID = s.PERSON_ID
JOIN GRADE g ON s.ID = g.STUDENT_ID
JOIN COURSE c ON g.COURSE_ID = c.ID
-- WHERE ... other complex code here
)
You might then continue your query with ...
-- 1st entry: Number of students on the last name 'E'
SELECT
'Last names beginning with letter "E"' AS Description,
count(distinct sg1.id) AS Number_of_students
FROM student_grades sg1
WHERE sg1.lastname LIKE 'E%'
UNION
-- 2nd entry: Number of students older than 20 years
SELECT
'Older than 20 years' AS Description,
count(distinct sg2.id) AS Number_of_students
FROM student_grades sg2
WHERE sg2.AGE > 20
UNION
-- 3rd entry: Number of female persons, including but not limited to students
-- NOTE: THIS ONE MATCHES YOUR ORIGINAL, WHICH IS INCORRECT
SELECT
'All female persons' AS Description,
count(distinct sg3.id) AS Number_of_students
FROM student_grades sg3
WHERE sg3.GENDER = 'Female'
UNION
-- 4th entry: Students who passed the course "Intermediate Norwegian" with grade B or higher
SELECT
'Passed "Intermediate Norwegian" with grade >= B' AS Description,
count(distinct sg4.id) AS Number_of_students
FROM student_grades sg4
WHERE
sg4.COURSE_NAME = 'Intermediate Norwegian'
AND sg4.grade IN ('A', 'B')
And that's actually a significant improvement. Note in particular that you don't need to pass conditions, subquery or not, into the CTE; instead, you query the CTE (which you could also join to other tables, etc.). Of course, in part that's because your "inner" subqueries were a pretty horrible way of doing things; instead, I use count(distinct sg.id), which achieves the same thing as those subqueries as long as person.id is non-null, which I presume it is on account of being a PK.
But note also that even the need for a distinct count (and the bugginess of the third part of the query) arise from trying to do all four parts with the same common intermediate results in the first place. You don't need to join course or grade information in order to query information related strictly to personal characteristics, and as long as student has a 0,1:1 relationship with person, leaving out the course and grade information would give you a distinct count for free.
And as for the third part, joining the student table restricts your results to students, which you didn't want. The fact that you don't put that restriction in the "inner" subquery is irrelevant; you're using that subquery to filter results that only include people who are students in the first place. Thus, *your approach cannot produce the results you want in this case.**
Maybe your desire to factor out a big chunk of common query arises from the mysterious "other complex code". I don't see how such a thing applies to the question as you've presented it, but I'm inclined to suspect that you would be better off finding a way -- or maybe separate ways per item -- to simplify or eliminate that code. If it were the case that that code could be ignored then I might write your query like so:
WITH student_person AS (
SELECT
p.lastname AS lastname,
p.age AS age,
p.gender AS gender,
s.id AS student_id
FROM
PERSON p
JOIN STUDENT s ON p.ID = s.PERSON_ID
)
-- 1st entry: Number of students on the last name 'E'
SELECT
'Last names beginning with letter "E"' AS Description,
count(*) AS Number_of_students
FROM student_person sp1
WHERE sp1.lastname LIKE 'E%'
UNION ALL
-- 2nd entry: Number of students older than 20 years
SELECT
'Older than 20 years' AS Description,
count(*) AS Number_of_students
FROM student_person sp2
WHERE sp2.AGE > 20
UNION ALL
-- 3rd entry: Number of female persons, including but not limited to students
-- NOTE: THIS ONE MATCHES YOUR ORIGINAL, WHICH IS INCORRECT
SELECT
'All female persons' AS Description,
count(*) AS Number_of_students
-- must select from PERSON, not STUDENT_PERSON:
FROM person p2
WHERE p2.GENDER = 'Female'
UNION ALL
-- 4th entry: Students who passed the course "Intermediate Norwegian" with grade B or higher
SELECT
'Passed "Intermediate Norwegian" with grade >= B' AS Description,
count(distinct sp3.student_id) AS Number_of_students
FROM student_person sp3
JOIN grades g ON sp3.student_id = g.student_id
JOIN course c ON g.course_id = c.id
WHERE
c.COURSE_NAME = 'Intermediate Norwegian'
AND g.grade IN ('A', 'B')
Take it from here, I'm sure you'll manage.
select count(case when lastname like 'e%' then 1 end) as lastname_starts_with_e
,count(case when age > 20 then 1 end) as age_greater_than_20
,count(case when gender = 'Female' then 1 end) as is_female
from person
;

SQL query returns student id, course section id, and grade when section id 1000

Using Oracle Apex Browser, image of database
http://imgur.com/a/Hhblp#0
select s_ID, c_sec_ID, grade
from s_ID.ID, c_sec_ID.csID, grade.ID, grade.csID
where c_sec_ID = 1000
^ All I think of and I'm not sure if I'm suppose to join them together or group them either.
You have to join these tree tables COURSE_SECTION, ENROLLMENT and STUDENT to get desired output. Put INNER JOIN on three tables and add
Where Clause to filter records.
You can try this
SELECT S.s_ID, C.c_sec_ID, E.grade
FROM COURSE_SECTION C INNER JOIN ENROLLMENT E ON C.C_SEC_ID = E.C_SEC_ID
INNER JOIN STUDENT S ON S.S_ID = E.S_ID
WHERE C.C_SEC_ID = 1000

SQL query get course number for certain student grades

I'm working through some problems and I can't seem to get the expected results for this one. The question is below with what is in my code right now and also the expected results. If anyone help that would be great. I'm just trying to get a understanding on this and can't seem to get my head around what exactly this is asking as you can see my code I have now isn't close to what the expected result is as of right now. Also I added the schema this will show whats in what table if needed for your guys help.
Question:
List the course number of courses wherein students have received grades for every one of the possible defined grade types. Order by course number.
My code so far:
SELECT g.Student_id, g.Grade_type_code
FROM Grade g LEFT OUTER JOIN Section s
ON g.Section_id = s.Section_id
GROUP BY g.Student_id, g.Grade_type_code
ORDER BY g.Student_id;
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer
Here is the Expected Result
COURSE_NO
----------
20
25
100
120
122
125
130
135
Note: The Chapter for this problem is based off using
LEFT OUTER JOIN
My Current results
STUDENT_ID GRADE_TYPE_CODE
---------- ---------------
102 FI
102 HM
102 MT
102 PA
102 QZ
103 FI
103 HM
103 MT
103 PA
103 QZ
104 FI
104 HM
Based on your ER diagram I believe this query should return a list of courses whose enrolled students have collectively received all of the grade types listed in the GRADE_TYPE table.
select s.course_no,
c.descr,
count(distinct g.grade_type_code) as num_grade_types
from grade g
join enrollment e
on g.student_id = e.student_id
and g.section_id = e.section_id
join section s
on e.section_id = s.section_id
join course c
on s.course_no = c.course_no
group by s.course_no, c.descr
having count(distinct g.grade_type_code) = (select count(grade_type_code)
from grade_type)
I didn't notice your expected result was only the course # (you can just get rid of the columns you don't want from the select list). Also the join to the COURSE table is only there to get the course description, so if you don't want the course description selected, you do not need that join.
You need to select COURSE_NO instead. And also use JOIN and not LEFT JOUTER JOIN.
Something like this:
select COURSE_NO from
(
SELECT distinct (s.COURSE_NO)
FROM Grade g JOIN Section s
ON g.Section_id = s.Section_id
)
ORDER BY s.COURSE_NO;

A Query of certain students in a certain room

I'm working through a problem, working with SQL Oracle. For some reason I am getting the result of 13 for instructors teaching that course when I should only be getting 1. There is only 1 Instructor that teaches only 3 or more students in that room.
Question:
Create a query to determine the number of instructors who have taught more than 3 students in a course section taught in Room L211.
My Code:
SELECT COUNT(Instructor_Id) AS NumberOfInstructors
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211'
HAVING COUNT(Student_Id) =
(SELECT COUNT(Student_iD)
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211')
ORDER BY s.Course_No;
My Results:
NUMBEROFINSTRUCTORS
-------------------
13
Expected Results:
NUMBEROFINSTRUCTORS
-------------------
1
So I feel like Im kind of going in the right direction maybe not but I feel like it is adding all the instructors up that teach in that class. Ive messed around a lot with that code I've given so if anyone can point me in the right direction. I'm guessing I need to have a count for counting the students who have been in a class with that room number I think I've tried that and got a result of 4 so I'm not sure.
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer
try this:
SELECT COUNT(*) NumberOfInstructors
From (Select Instructor_Id
FROM Section s
join Enrollment e
on e.Section_Id = s.Section_Id
WHERE s.Location = 'L211'
Group By Instructor_Id
HAVING COUNT(Student_Id) >= 3) Z
I think you are needing a subquery here. In pseudo code, you are looking for a query like:
SELECT count(teachers)
FROM
(SELECT count(students) FROM enrollment where location='L211') AS "numberofstudents"
WHERE numberofstudents > 3
I did not test the code, but it seems to me you are missing a condition. Your question requires you to find "Instructor that teaches only 3 or more students in that room", but you did not have any condition to check for student number > 3. Try this:
SELECT COUNT(Instructor_Id) AS NumberOfInstructors
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211'
HAVING COUNT(Student_Id) =
(SELECT COUNT(Student_iD)
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211' AND COUNT(Student_Id) > 3)
ORDER BY s.Course_No;