Summing Course Credits with Business Logic - sql

In order for a student to graduate high school with "emphasis" in a particular subject, they must complete at least one introductory course in a defined list that area of expertise, and three core courses. I am trying to show the number of credits a student has and can be used to assign a student an "emphasis". The problem is a student can take numerous introductory level classes that might apply to an area of expertise, but only one of them should count towards the total credits. Or visa-versa, they can't be assigned if they have four core courses but no introductory class. Here is my statement below. Everything about this is telling me it is a loop, but I understand that opening cursors is bad for performance, and there is a 't-sql' way of doing this most likely.
Select * From (Select ks.ks_id,
ks.student_name,
ks.current_grade_level,
cpp.title,
ks.bldg_id,
Sum(cc.credits) as 'Sum of Credits Per Expertise' ,
From #KidsSilo as ks
Inner Join cpp_courses as cc on cc.statecourse_id = ks.statecourse_id --- cpp_courses contains all the courses
Inner Join #Cpp_expertise as cpp on cc.cpp_ID = cpp.cpp_id --- cpp_expertise contains all the Expertise' and the id link to each cpp_courses
Where cpp.bldg_id = ks.bldg_id
--And cc.course_level = 'X' -- Introductory, Would a nested Select Top 1 1 work for this piece?
--And cc.course_level = 'C' -- Core classes
Group by
ks.ks_id,
cpp.title,
ks.student_name,
ks.current_grade_level,
ks.bldg_id) as t
Inner Join #Districts as d on d.bldg_id = t.bldg_id
order by t.bldg_id desc

If you have a courses table with:
course id
expertise
level
And an enrollment table that has students in the courses, then I would expect a query like this:
select e.student_id, c.expertise
from enrollment e join
courses c
on e.course_id = c.course_id
group by e.student_id, c.expertise
having sum(case when level = 'introductory' then 1 else 0 end) >= 1 and
sum(case when level = 'core' then 1 else 0 end) >= 3;
Your data model is very unclear, but perhaps this will help.

Related

How do i make this query with two conditions?

database structure
Professor
prof_id number
name string
salary number
building string
Course
name string
prof_id number
room_number number
start_time number
end_time number
Room
room_number number
capacity number
building string
Professors with at least 1 math course:
select distinct Professor.name, count(Course.name) AS numberOfMathCourses
from Course
LEFT JOIN Room
ON Course.Room_id = Room.Room.id
INNER JOIN Professor
ON Professor.id = Course.id
where Course.name = 'math'
group by Professor.name
having numberOfMathCourses > 0
Professors with less than 3 courses :
select distinct Professor.name, count(Course.name) AS numberOfCourses
from Course
LEFT JOIN Room
ON Course.Room_id = Room.Room.id
INNER JOIN Professor
ON Professor.id = Course.id
group by Professor.name
having numberOfCourses < 3
how would do I create a Query that has both of these conditions ? more than one math course course and less than 3 courses.
I tried sub-queries but I wasn't able to make it work. I will try to look into it more. Thanks for the help.
select *
from Professor p
where
(select count(*) from Course c where p.prof_id = c.prof_id and c.name = 'math') > 0
and (select count(*) from Course c where p.prof_id = c.prof_id) < 3

improving the sql query for a dbms problem

I want to write a SQL query for the problem as defined below, my answer for the first part is as below, but I am not sure about the answer, can anyone help me? Is the answer correct, or if not, how can I improve it?
For the second part can anyone help me?
Let us consider the following relational schema about physicians and departments:
PHYSICIAN (PhysicianId, Name, Surname, Specialization, Gender, BirthDate, Department);
Let every physician be univocally identified by a code and characterized by a name, a surname, a specialization (we assume to record exactly one specializa- tion for each physician), a gender, a birth date, a department (each physician is assigned to one and only one department), and a home city.
Let every city be univocally identified by its name and characterized by the region it belongs to.
DEPARTMENT (Name, Building, Floor, Chief)
Let every department be univocally identified by a name and characterized by its location (building and floor) and chief. Let us assume that a physician can be the chief of at most one department (the department he/she belongs to). We do not exclude the possibility for two distinct departments to be located at the same floor of the same building.
BELONGS TO(City,Region)
Let us assume that a physician can be the chief of at most one department (the department he/she belongs to). We do not exclude the possibility for two distinct departments to be located at the same floor of the same building.
I want to formulate an SQL query to compute the following data (exploiting aggregate functions only if they are strictly necessary):
• the departments such that (i) all their physicians reside in the region Piemonte and (ii) at least one of them resides in the city Torino.
My answer for the fist part is as below:
for the second part, I don't know how to solve it .
create view X{
select b.city, b.region, b.department
from physician p inner join belong-to b on b.city= p.homecity
where b.region="Piemonte"
select name
from department d
where exists( select*
from X
where p.department =d.name )
I think this should work:
Select Distinct b.department
From physician p
Join belong-to b on b.city = p.homecity
Where Exists (Select 1 From belong-to b2 Where b2.city = p.homecity AND b2.region = "Piemonte")
Exists (Select 1 From belong-to b2 Where b2.city = p.homecity AND b2.city = "Torino") AND
NOT Exists (Select 1 From belong-to b2 Where b2.city = p.homecity AND b2.region <> "Piemonte") AND
I would use aggregation:
select p.department
from physician p join
belongsto b
on p.homecity = b.city
group by p.department
having sum(case when b.region <> 'Piemonte' then 1 else 0 end) = 0 and -- no Piemonte
sum(case when p.homecity = 'Torino' then 1 else 0 end) >= 1 -- at least one Torino

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
;

Tricky statistics select

Trying to figure out this for a while, but no luck. I have the following tables (MS-SQL 2008):
students
studentID – email – profileID
courses
courseID – name
studentsCourses
studentID – courseID
profiles
profileID – name
profilesMandatoryCourses
profileID - courseID
studentsCoursesLogs
logID - studentID –courseID – accessDate
Each student, when enrolls, is assigned a profile. For each profile there are a number of mandatory course. Those mandatory courses together with any other courses a user takes are saved in the studentsCourses table.
Whenever a student accesses a course the information is logged in the studentsCoursesLogs.
I am trying to figure out all the students that have taken all the mandatory courses based on their profile.
Any pointers appreciated.
I think this should do it (assuming I've understood your data model and requirements correctly - and that in throwing this sample SQL together I've not made any mistakes - I didn't create your data model in a database). I'm pretty sure it should be close enough though.
select studentRecords.StudentId,
sum(case TakenCourseID when null then 0 else 1 end) as CompleteMandatoryCourses,
sum(case TakenCourseID when null then 1 else 0 end) as IncompleteMandatoryCourses
from (
select mandatoryCourses.StudentID, mandatoryCourses.CourseId as MandatoryCourseID, takenCourses.CourseID as TakenCourseID
from ( -- Courses student should have taken - based on their profile
select p.[Profile], pmc.CourseID, s.StudentID
from profiles p
inner join profilesMandatoryCourses pmc on p.ProfileID = pmc.Profile
inner join students s on p.StudentID = s.StudentID
) mandatoryCourses
left join
(
-- Course students have taken
select s.StudentID, s.ProfileID, sc.CourseID
from students s
inner join studentsCourseLogs scl on s.StudentID = scl.StudentID
) takenCourses on mandatoryCourses.ProfileId = takenCourses.ProfileID
and mandatoryCourses.CourseID = takenCourses.CourseID
) studentRecords
group by mandatoryCourse.StudentId
having sum(case TakenCourseID when null then 1 else 0 end) = 0
It will give a recordset like the following....
+----------------+-----------------------------+-------------------------------+
| StudentID | CompleteMandatoryCourses | IncompleteMandatoryCourses |
+----------------+-----------------------------+-------------------------------+
| 1 | 15 | 3 |
| 2 | 8 | 0 |
+----------------+-----------------------------+-------------------------------+
If you just want a list of students who have taken all mandatory courses, you could wrap all of the above as shown here...
select studentID
from ( /* insert very long sql here */ )
where IncompleteMandatoryCourses = 0`

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.