How to calculate a GPA from grades and credits? - sql

I want to calculate a GPA for each student from grades and credits. I have executed something like this
SET GPA=(SELECT((t.grade*c.credits)/c.credits)
FROM Student s, Take t, Courses c
WHERE s.sid=t.sid and t.cid=c.cid)
It doesn't work. The Select query's result has a lot of rows. So I couldn't finish this. My tables are like these:
take (sid, cid, grade)
course (cid, credits)
student (sid,gpa)
I'm using PostgreSQL.

Try this:
SELECT s.sid, (SUM(t.grade * c.credits) / SUM(t.credits)) AS GPA
FROM Student s
INNER JOIN Take t ON s.sid = t.sid
INNER JOIN Courses c ON t.cid = c.cid
GROUP BY s.sid

Related

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.

Selecting SQL data based on multiple separate tables

I'm doing some SQL practice and have been stumped by the following question.
I'm given the database schema:
Course (Course#, title, dept)
Student (Student#, name, program)
Enrolled (Student#, Course#, grade)
I'm trying the translate the following statement to SQL:
List the names of all students who takes Computer courses or Science courses.
Initially I thought the answer might be something like this:
SELECT Sname
FROM Course,Student,Enrolled
WHERE Course.dept = "Computer" OR Course.dept = "Science"
However, I feel like the rows in the table are not joined quite how I imagined, and that there is something off with this. How far off am I?
This is not that simple: first, you need to join the tables, and then you need to group by name to eliminate duplicates:
SELECT s.name
FROM Student s
JOIN Enrolled e ON s.Student#=e.Student#
JOIN Course c ON e.Course#=c.Course#
WHERE c.dept = 'Computer' OR c.dept = 'Science'
GROUP BY s.name
GROUP BY is necessary because the same student may be taking both "Computer" and "Science" courses, in which case JOIN would produce multiple records for the same student. In this case you have an option of replacing it with DISTINCT.
If you have 2 courses Computing (1) and Science (2) with the IDs 1 and 2, you need to do a query like this:
SELECT s.first_name, s.last_name FROM students s JOIN enrolled e ON e.student_id = s.id WHERE e.course_id IN(1, 2)
Sorry may have misread, if you need to do it by course type and the courses are tagged as dept = Computer, Science, Literacy etc... Do the following query:
SELECT s.first_name, s.last_name FROM students s JOIN enrolled e ON e.student_id = s.id JOIN courses c ON c.id = e.course_id WHERE c.dept IN('Computing', 'Science')
Or if you want to do an OR:
SELECT s.first_name, s.last_name FROM students s JOIN enrolled e ON e.student_id = s.id JOIN courses c ON c.id = e.course_id WHERE c.dept = 'Computing' OR c.dept = 'Science'

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;

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.