SQL Conditional SELECT from 2 tables - sql

I'm new to SQL and was hoping I could get some help with the following query.
I have 3 tables:
students(name, student_id);
exam_results(module_code,student_id,grade);
projects(module_code,student_id,grade).
I would like to SELECT the student name, grade and module_code, however some modules have both an exam AND a project associated with them. So if that is the case, I want to grade to be the average from projects and exams, otherwise just the grade from exams.
Any ideas?

SELECT s.student_id, s.name, m.module_code, avg(m.grade) as grade from students s
inner join
(select module_code, student_id, grade from exam_results
union all select module_code, student_id, grade from projects) as exam_module m
on s.student_id = m.student_id
group by s.student_id, s.name, m.module_code

Related

SQL MAX() grouping

Tables:
student(sid, sname, sex, age, year, gpa)
major(dname, sid)
Question:
For each department with more than 15 students majoring in the department, we want to print information about the student(s) with the highest GPA within the department. In particular, for each such student, we want to print the student id, student name and GPA, and the department name the student is major in.
So far I have:
SELECT student.sid, student.sname, student.gpa, major.dname
FROM student
RIGHT JOIN major ON student.sid = major.sid
WHERE student.gpa IN (
SELECT MAX(gpa)
FROM student JOIN major ON student.sid = major.sid
GROUP BY dname
HAVING COUNT(dname) > 15
)
But it doesn't give me the accurate query. The clause inside the IN works but when put together in this way it doesn't actually match student.gpa to dname max GPA. What am I doing wrong here?
This Query gives:
enter image description here
I need:
enter image description here
Your inner query gives you the maximum GPA for each department. Then the outer query returns all students who have a GPA equal to any of the maximum GPA's, regardless of the department. The quickest fix of your code is to use a correlated subquery, that will find the maximum GPA for the student's specific department.
SELECT s.sid, s.sname, s.gpa, sm.dname
FROM student s
RIGHT JOIN major sm ON s.sid = sm.sid
WHERE student.gpa IN (
SELECT MAX(ds.gpa)
FROM student ds JOIN major dm ON ds.sid=dm.sid
WHERE dm.dname = sm.dname
GROUP BY dm.dname
HAVING COUNT(dm.dname)>15
)
This query:
select dname, max(s.gpa) maxgpa
from major m inner join student s
on s.sid = m.sid
group by dname
having count(s.sid) > 15
returns all the departments with more than 15 students and the highest gpa in that department.
Join it to the 2 tables like this:
select s.sid, s.sname, s.gpa, t.dname
from (
select dname, max(s.gpa) maxgpa
from major m inner join student s
on s.sid = m.sid
group by dname
having count(s.sid) > 15
) t
inner join major m on m.dname = t.dname
inner join student s on s.sid = m.sid and s.gpa = t.maxgpa
Or with window functions:
select t.sid, t.sname, t.gpa, t.dname
from (
select m.dname, s.*,
rank() over (partition by m.dname order by s.gpa desc) rn,
count(s.sid) over (partition by m.dname) counter
from major m inner join student s
on s.sid = m.sid
) t
where t.counter > 15 and t.rn = 1
If you change your WHERE ... IN statement to be an INNER JOIN, you can connect more fields.
SELECT student.sid, student.sname, student.gpa, major.dname
FROM student
RIGHT JOIN major
ON student.sid = major.sid
INNER JOIN (
SELECT MAX(gpa) as max_gpa, dname
FROM student JOIN major ON student.sid=major.sid
GROUP BY dname
HAVING COUNT(dname)>15
) as dept_gpa
ON student.gpa = dept_gpa.max_gpa
AND major.dname = dept_gpa.dname

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.

SQL query (GROUP BY)

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;

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;

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