SQL query (GROUP BY) - sql

I have the following DB outlint:
GRADE (SID, CID, Semester, Year, Grade)
STUDENT (SID, Name, Major)
First question is this:
For each student, list student’s name and the number of courses successfully completed.
Note that a course is successfully completed if a student has earned a grade other than “W” or “F”. Also, there may be two or more students in the database having same names.
I have the following query, but I am not sure if it is correct. I don't have data to test against due to being required to only use the construct of the DB to answer. Can I group by SID even if I don't specify SID in my select clause?
SELECT Name, COUNT(SID)
FROM STUDENT S, GRADE G
WHERE S.SID = G.SID
AND G.Grade != "W"
AND G.Grade != "F"
GROUP BY SID, Name;

You have to group by SID, since your instructions clearly state that students may have the same name.
You have to proceed with an OUTER join with grades, otherwise how to display students with 0 courses completed?
the COUNT should then apply to the whole group, there's no need to specify any column in there.
Query:
SELECT s.SID, s.Name, COUNT(*)
FROM
STUDENT S
LEFT JOIN GRADE G ON S.SID = G.SID AND G.Grade != "W" AND G.Grade != "F"
GROUP BY s.SID, s.Name;
But now, I think you omitted some information. I believe there is a course table as well, am I correct? Because as it stands, this query returns the number of compatible grades, not the number of passed courses.
Also, if you need to display the name ONLY, then you'll have to include this query in a subquery:
Query:
SELECT t.Name, t.cnt FROM (
SELECT s.SID, s.Name, COUNT(*) as cnt
FROM
STUDENT S
LEFT JOIN GRADE G ON S.SID = G.SID AND G.Grade != "W" AND G.Grade != "F"
GROUP BY s.SID, s.Name
) t;

Related

SQL on Oracle: Already joined two tables, now i want another column from another table(another join)

I have three tables;
1. Students - id, name
2. Subjects - sid,sname
3. Results - id,sid,marks (id and sid are foreign keys referenced by the two tables above)
Now, i perform
SELECT s.sname AS SubjectName, MAX(r.marks) AS MaxMarks
FROM subjects s, results r
WHERE s.sid=r.sid
GROUP BY r.sid, s.sname
ORDER BY r.sid
and i get The Subject Name with the maximum marks scored in them.
Now further, i also want the student name that has scored these max marks.
So i tried adding the column r.id, didn't work. I tried adding the table students in this query. I'm probably goofing up with the grouping after adding the table or something?
I did this
SELECT r.id AS StudentID, s.sname AS SubjectName, MAX(r.marks) AS MaxMarks
FROM subjects s, results r
WHERE s.sid=r.sid
GROUP BY r.sid, s.sname, r.id
ORDER BY r.sid
and i got each StudentID, with repeated subjects and the marks scored.
Whereas what i basically want is the student who has scored the highest in each subject.
you may use ROW_NUMBER() to tag the student who marked the highest on each subject
SELECT st.name,
sj.sname,
r.marks
FROM (SELECT id,
sid,
marks,
ROW_NUMBER() OVER (PARTITION BY sid
ORDER BY marks DESC) maxmarks
FROM results) r
JOIN students st
ON r.id = st.id
JOIN subjects sj
ON r.sid = sj.sid
WHERE r.maxmarks = 1
you can simple add the new join, and i suggest you the use of explicit join sintax
SELECT t.name, s.sname AS SubjectName, MAX(r.marks) AS MaxMarks
FROM subjects s
INNER JOIN results r ON s.sid=r.sid
INNER JOIN Students t ON t.id = r.id
GROUP BY r.sid, s.sname, t.name
ORDER BY r.sid
In the implicit join sintax should be
SELECT s.sname AS SubjectName, MAX(r.marks) AS MaxMarks
FROM subjects s, results r, stundet t
WHERE s.sid=r.sid
and t.id = r.id
GROUP BY r.sid, s.sname, t.name
ORDER BY r.sid

SQL not group by expression, Find student who has taken at least 5 courses

select s.name, s.id
from student s join takes t on t.id = s.id
where s.name like 'D%'
group by s.name, s.id
having (
select count(distinct c.course_id)
from course c
where c.dept_name = 'History' and c.course_id = t.course_id)>4
order by s.name
I am confused about how GROUP BY works. I am trying to find the students who has taken at least 5 courses from history department and name start with D.
Not sure with the nested subqueries...
course(course id, title, dept name, credits)
student(ID, name, dept name, tot_cred)
takes(ID, course_id, sec_id, semester, year, grade)
You have to additionally JOIN with course table:
select s.name, s.id
from student s
inner join takes t on t.id = s.id
inner join course c on c.course_id = t.course_id
where s.name like 'D%' and c.dept_name = 'History'
group by s.name, s.id
having count(distinct c.course_id) >= 5
The WHERE clause returns all students whose names start with a 'D' and have taken at least one course in history department. The HAVING clause filters out any students with 4 or less distinct courses in history department.

Using max() in SQL

One part of my homework assignment is to find the student with the highest average from each department.
QUERY:
SELECT g.sid as studentID, s.sfirstname, s.dcode, AVG(grade) as average
FROM studentgrades g, student s
WHERE g.sid = s.sid
GROUP BY s.sid
RESULT:
1 Robert ger 80.0000
2 Julie sta 77.0000
3 Michael csc 84.0000
4 Julia csc 100.0000
5 Patric csc 86.0000
6 Jill sta 74.5000
To answer The question, I ran the query
SELECT dcode, averages.sfirstName, MAX(averages.average)
FROM (
SELECT g.sid as studentID, s.sfirstname, s.dcode, AVG(grade) as average
FROM studentgrades g, student s
WHERE g.sid = s.sid
GROUP BY s.sid) averages
GROUP BY dcode
RESULT:
csc Michael 100.0000
ger Robert 80.0000
sta Julie 77.0000
Even though the averages are correct, the names are not!
Julia is the one who has the average 100 in csc, so why does Michael show up?
Here's an example:
a student takes courses and gets grades for these courses. EG:
student1 from dept1 took course A and got grade 80
student1 from dept1 took course B and got grade 90
student2 from dept1 took course C and got grade 100
student3 from dept2 took course X and got grade 90
AFTER RUNNING THE FIRST QUERY we get the averages for each student
student 1 from dept1 has average 85
student 2 from dept1 has average 100
student 3 from dept2 has average 90
Now we find the student with the highest average from each department
dept1, student2, 100
dept2, student3, 90
This should do it (and it uses the GROUP BY according to the SQL standard, not the way MySQL implements it)
select s.sid,
s.sfirstname,
s.dcode,
ag.avg_grade
from students s
join (select sid, avg(grade) as avg_grade
from studentgrades
group by sid) ag on ag.sid = s.sid
join (select s.dcode,
max(avg_grade) max_avg_grade
from students s
join (select sid, avg(grade) as avg_grade
from studentgrades
group by sid) ag on ag.sid = s.sid
group by s.dcode) mag on mag.dcode = s.dcode and mag.max_avg_grade = ag.avg_grade
order by mag.avg_grade;
How this works
This builds up the result in several steps. First it calculates the average grade for each student:
select sid, avg(grade) as avg_grade
from studentgrades
group by sid
Based on the result of this statement, we can calculate the max. average grade:
select s.dcode,
max(avg_grade) max_avg_grade
from students s
join (select sid, avg(grade) as avg_grade
from studentgrades
group by sid) ag on ag.sid = s.sid
group by s.dcode
Now these two results are joined to the students table. For easier reading assume there is a view called average_grades (the first statement) and max_average_grades (the second one).
The final statement basically does this then:
select s.sid,
s.sfirstname,
s.dcode,
ag.avg_grade
from students s
join avg_grades ag on ag.sid = s.sid
join max_avg_grades mag
on mag.dcode = s.dcode
and mag.max_avg_grade = ag.avg_grade;
The real one (the very first in my answer) simply replaces the names avg_grades and max_avg_grades with the selects I have shown. That's why it looks so complicated.
A solution in standard SQL that is a bit more readable
In standard SQL, this could be expressed using a common table expression which makes it a bit more readable (but is essentially the same thing)
with avg_grades (sid, avg_grade) as (
select sid, avg(grade) as avg_grade
from studentgrades
group by sid
),
max_avg_grades (dcode, max_avg_grade) as (
select s.dcode, max(avg_grade) max_avg_grade
from students s
join avg_grades ag on ag.sid = s.sid
group by s.dcode
)
select s.sid,
s.sfirstname,
s.dcode,
ag.avg_grade
from students s
join avg_grades ag on ag.sid = s.sid
join max_avg_grades mag on mag.dcode = s.dcode and mag.max_avg_grade = ag.avg_grade;
But MySQL is one of the very few DBMS to not support this, so you will need to stick with the initial statement.
A standard SQL solution requiring less derived tables
In standard SQL it could be written even a bit shorter using windowing functions to calculate the rank inside a department (again this does not work in MySQL)
with avg_grades (sid, avg_grade) as (
select sid, avg(grade) as avg_grade
from studentgrades
group by sid
)
select sid,
sfirstname,
dcode,
avg_grade
from (
select s.sid,
s.sfirstname,
s.dcode,
ag.avg_grade,
rank() over (partition by s.dcode order by ag.avg_grade desc) as rnk
from students s
join avg_grades ag on ag.sid = s.sid
) t
where rnk = 1;
Update the query to use a HAVING clause as below:
SELECT dcode, averages.sfirstName, averages.average
FROM (
SELECT g.sid as studentID, s.sfirstname, s.dcode, AVG(grade) as average
FROM studentgrades g, student s
WHERE g.sid = s.sid
GROUP BY s.sid) averages
GROUP BY dcode
HAVING MAX(averages.average) = averages.average
there are many different solutions.
Maybe this one is simpler to understand:
/* create a new temporariy table of student performance. to keep the code clean and the performance better */
INSERT INTO studentperformance (studentID, sfirstname, dcode, average)
SELECT g.sid as studentID
, s.sfirstname
, s.dcode
, AVG(grade) as average
FROM studentgrades g, student s
WHERE g.sid = s.sid
GROUP BY s.sid;
/* best grades for each department */
INSERT INTO bestgrades (best_average_per_department)
SELECT (dcode + '|' + MAX(average)) as best_average_per_department /* important string. maybe one has to cast the max(average) to string for this to work */
FROM studentperformance
GROUP BY dcode; /* important groub by ! */
/* get all the students who are best in each department */
SELECT a.studentID
, a.sfirstname
, a.dcode
, a.average
FROM studentperformance as a
JOIN bestgrades as b on (a.dcode + '|' + a.average) = b.best_average_per_department;

Finding total participation in sql

This is a question I could not answer in oracle lab exam.
Given the schema:
(Courses: cid(int), deptid(int)...);
(Students: sid(int), sname (string), deptid(int)...);
(Participation: cid(int), sid(int), ...);
A student can attend courses outside his department.
Need to get the names of the students who take all the courses offered by his department.
How to do this in sqlplus?
SELECT s.sid, s.sname, s.deptid
FROM Students s
INNER JOIN Participation p
ON s.sid = p.sid
INNER JOIN Courses c
ON p.cid = c.cid
AND s.deptid = c.deptid
GROUP BY s.sid, s.sname, s.deptid
HAVING COUNT(DISTINCT c.cid) = (SELECT COUNT(*)
FROM Courses c2
WHERE c2.deptid = s.deptid)
I cannot tesst the query right now, so I don't know if I have a syntactic error, anyway, you can try this idea to achieve your requirements.
SELECT studentName
FROM
(SELECT stu.sname AS studentName,
cour.deptid AS dept,
COUNT(*) AS assistedCoursesByDpt,
Max(cour.total) AS total
FROM students stu,
participation part,
(SELECT cour.deptid,COUNT(*) AS total FROM courses cour GROUP BY cour.deptid
) AS cour
WHERE stu.sid=part.sid
AND part.cid =cour.cid
GROUP BY stu.sid,
cour.deptid
)
WHERE total=assistedCoursesByDpt
Th idea is to create a subquery (cour) that has a new calculated column, the total courses by debt. Then you can compare this total with the agrouped student courses by dept.

SQL AVG AND JOIN ORACLE

I have the following task: Find the students with the average of grades greater than the average of all grades (All the grades of all the students are on the same column). Moreover I have to get from a different table his name.
The first table looks like this:
Student_ID GRADE Course_Name
50 10 Math
60 9 Math
100 10 Math
200 7 Math
50 8 Sport
100 7 Sport
and so on...
And in the second table:
Student_ID Name
50 JHON
60 Mark
100 FIONA
200 ROBERT
I get my head tied up in the order of the things I should do. Can you help me with the sql oracle code and give me an explanation on how you thought in the process? Thank you in advance.
Here is an option using analytic functions:
SELECT t.*
FROM
(
SELECT s.*, g.*,
AVG(g.GRADE) OVER (PARTITION BY s.Student_ID) avg_s,
AVG(g.GRADE) OVER () avg_all
FROM students s
LEFT JOIN grades g
ON s.student_ID = g.student_ID
) t
WHERE avg_s > avg_all
Edit:
If you just want to report each student alone who meets the grade criteria you may try doing SELECT DISTINCT Name in the outer select.
To get the average grade of each student join the two tables and GROUP BY the student ID and name. To get the average of all the grades you need a separate query, because it's a different qranularity. You can use this as a sub-query in the HAVING clause of the GROUP BY.
select g.student_id
, s.name
, avg(g.grade) as student_avg
from grades g
join students s
on g.student_id = s.student_id
group by g.student_id, s.name
having avg(g.grade) > ( select avg(grade) from grades);
Here is a SQL Fiddle demo.
One solution is
select s.student_id, s.name, avg(g.grade)
from grades g
inner join students s on s.student_id = g.student_id
group by s.student_id, s.name
having avg(g.grade) > (select avg(grade) from grades)
On the thought process:
First, you need to realize that the names are not important for the task, you can just join them with the result of the average computation as last step. So focus on that one.
SELECT name, avg_grade
FROM ( <query to get the averages> ) q
JOIN students ON q.student_id = name_table.student_id
Second, Average of all grades is just one number, it can go in a subselect in a WHERE or HAVING condition. It also is just
SELECT AVG(grade) from grades
Third, you need the average grade of every student. This can be achieved with GROUP BY.
SELECT student_id, AVG(grade)
FROM grades
GROUP BY student_id
Tying everything together:
SELECT name, avg_grade
FROM (
SELECT student_id, AVG(grade) as avg_grade
FROM grades
GROUP BY student_id
HAVING AVG(grade) > (SELECT AVG(grade) from grades)
) q
JOIN students ON q.student_id = name_table.student_id