Sql Query to fetch highest marks - sql

The table structure is like this:
Student (StudentID int, StudentName varchar(20))
Subject (SubjectID int, SubjectName varchar(20))
Score (ScoreID int, StudentID int, SubjectID int, Score int)
Here's the data
Student
| StudentID | StudentName |
| 1 | John |
| 2 | Nash |
| 3 | Albert |
---------------------------
Subject
| SubjectID | SubjectName |
| 1 | Maths |
| 2 | Physics |
| 3 | Chemistry|
| 4 | English |
---------------------------
Score
| ScoreID | StudentID | SubjectID | Score |
| 1 | 1 | 1 | 34 |
| 2 | 1 | 2 | 45 |
| 3 | 1 | 3 | 56 |
| 4 | 2 | 1 | 78 |
| 5 | 2 | 3 | 23 |
| 6 | 2 | 4 | 44 |
| 7 | 3 | 1 | 45 |
| 8 | 3 | 2 | 10 |
| 9 | 3 | 3 | 54 |
| 10 | 3 | 4 | 74 |
-------------------------------------------
Output:
|StudentName | Score | SubjectName |
| John | 45 | Physics |
| John | 56 | Chemistry |
| Nash | 78 | Maths |
| Albert | 74 | English |
------------------------------------
I want to write a query to fetch the top scorer of every subject along with their scores and subject names, without using Row_Number(), Rank() and Dense_Rank().
I've written this query, but I think it can be improved:
select
st.StudentName, Score, B.SubjectID
from
(select
StudentID, Sc.SubjectID, Sc.Score
from
(select
SubjectID, MAX(Score) as 'Score'
from
Score Sc
inner join
subject sb on sc.subjectid = sb.subjectid
group by
SubjectID) A
inner join
score sc on sc.SubjectID = a.SubjectID and sc.Score = A.Score) B
inner join
Student st on st.studentID = B.StudentID

This is a solution without using RANK or ROW_NUMBER. Also this is much simplified to meet your output. Hope it helps
SELECT st.StudentName ,s.Score ,su.SubjectName FROM (SELECT
SubjectID,MAX(Score) as Max FROM Score GROUP BY SubjectID) a
INNER JOIN Score s on a.SubjectID=s.SubjectID AND a.MAX=s.Score
INNER JOIN Student st on s.StudentID=st.StudentID
INNER JOIN Subject su on s.SubjectID=su.SubjectID

You would use the ANSI standard rank() or row_number() functions. Assuming you want all duplicates, use rank():
select StudentName, SubjectName, Score
from (select st.StudentName, su.SubjectName, s.Score,
rank() over (partition by su.SubjectName order by s.Score desc) as seqnum
from score s join
student st
on s.studentid = st.studentid join
subject su
on s.subjectid = su.subjectid
) s
where seqnum = 1;
If you wanted exactly one row per subject with an arbitrary top student, use row_number().
In SQL Server, you can also do this without a subquery:
select top (1) with ties st.StudentName, su.SubjectName, s.Score
from score s join
student st
on s.studentid = st.studentid join
subject su
on s.subjectid = su.subjectid
order by rank() over (partition by su.SubjectName order by s.Score desc)
Another fun option is apply -- and this doesn't use window functions and can have quite good performance:
select ss.StudentName, su.SubjectName, ss.Score
from subject su cross apply
(select top (1) with ties s.*
from score s join
student st
on s.studentid = st.studentid join
where s.subjectid = su.subjectid
order by su.score desc
) ss;

You can use ROW_NUMBER like below
SELECT * FROM
(SELECT st.StudentName, sub.SubjectName, s.Score,
ROW_NUMBER() OVER(partition by sub.SubjectName order by s.Score Desc) as row_no
FROM Score s
LEFT OUTER JOIN Student st on s.StudentID = st.StudentID
LEFT OUTER JOIN Subject sub on s.SubjectID = sub.SubjectID) as tblMain
WHERE row_no = 1

Related

Postgresql left join

I have two tables cars and usage. I create a record in usage once a month for some of cars.
Now I want to get distinct list of cars with their latest usage that I saved.
first of all look at the tables please
cars:
| id | model | reseller_id |
|----|-------------|-------------|
| 1 | Samand Sall | 324228 |
| 2 | Saba 141 | 92933 |
usages:
| id | car_id | year | month | gas |
|----|--------|------|-------|-----|
| 1 | 2 | 2020 | 2 | 68 |
| 2 | 2 | 2020 | 3 | 94 |
| 3 | 2 | 2020 | 4 | 33 |
| 4 | 2 | 2020 | 5 | 12 |
The problem is here
I need only the latest usage of year and month
I tried a lot of ways but none of them is good enough. because sometimes this query gets me one ofnot latest records of usages.
SELECT * FROM cars AS c
LEFT JOIN
(select *
from usages
) u on (c.id = u.car_id)
order by u.gas desc
You can do this with a DISTINCT ON in the derived table:
SELECT *
FROM cars AS c
LEFT JOIN (
select distinct on (u.car_id) *
from usages u
order by u.car_id, u.year desc, u.month desc
) lu on c.id = lu.car_id
order by u.gas desc;
I think you need window function row_number. Here is the demo.
select
id,
model,
reseller_id
from
(
select
c.id,
model,
reseller_id,
row_number() over (partition by u.car_id order by u.id desc) as rn
from cars c
left join usages u
on c.id = u.car_id
) subq
where rn = 1

PostgreSQL - Max value for each id

I'm trying to get max value of exam_id from table exams for each protege.
proteges
protege_id | protege_patron | protege_firstname | protege_lastname
------------+----------------+-------------------+------------------
1 | 1 | Andrzej | Maniek
2 | 1 | Anna | Maj
3 | 1 | Joanna | Jankowska
exams
exam_id | exam_protege | exam_weight | exam_glucose | exam_pressure
---------+--------------+-------------+--------------+---------------
1 | 1 | 84 | 3ml | 123/84
2 | 1 | 99 | 23ml | 124/72
3 | 2 | 99 | 23ml | 124/72
4 | 3 | 94 | 23ml | 124/72
First I've tried
SELECT DISTINCT protege_patron, exams.*
FROM exams INNER JOIN proteges ON protege_id = exam_protege
WHERE exam_id = (SELECT MAX(exam_id) FROM exams WHERE protege_patron = 1);
and the output was:
protege_patron | exam_id | exam_protege | exam_weight | exam_glucose | exam_pressure
----------------+---------+--------------+-------------+--------------+---------------
1 | 4 | 3 | 94 | 23ml | 124/72
(1 row)
After trying SELECT protege_firstname, protege_lastname, MAX (exam_id) FROM exams JOIN proteges ON protege_id = exam_protege GROUP BY protege_id; the output is:
protege_firstname | protege_lastname | max
-------------------+------------------+-----
Andrzej | Maniek | 2
Anna | Maj | 3
Joanna | Jankowska | 4
(3 rows)
So, logical way was to add more things like exam_weight
That's what I did :
SELECT protege_firstname, protege_lastname, exam_weight, MAX (exam_id) FROM exams JOIN proteges ON protege_id = exam_protege GROUP BY protege_id;
ERROR: column "exams.exam_weight" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select protege_firstname, protege_lastname, exam_weight, MAX...
^
Atm I don't know how to fix that. Tried distinct, read some about aggregate functions... Is there any way to do that? All I want to do is to JOIN two tables and for each protege select all of his values and values of his exam with max exam_id...
You can to use distinct on. I think the logic is:
select distinct on (exam_protege) e.*
from exams e
order by exam_protege, exam_id desc;
You can, of course also bring in the protege information using a join:
select distinct on (exam_protege) e.*, p.*
from exams e join
protege p
on e.exam_protege = p.protege_id
order by exam_protege, exam_id desc;
You can do it like this:
select * from
(
select max(exam_id) maxexamid, exam_protege from exams group by exam_protege
) as maxexams
inner join proteges p
on maxexams.exam_protege = p.protege_id
inner join exams e
on e.exam_id = maxexams.maxexamid

How to handle duplicates created by LEFT JOIN

LEFT TABLE:
+------+---------+--------+
| Name | Surname | Salary |
+------+---------+--------+
| Foo | Bar | 100 |
| Foo | Kar | 300 |
| Fo | Ba | 35 |
+------+---------+--------+
RIGHT TABLE:
+------+-------+
| Name | Bonus |
+------+-------+
| Foo | 10 |
| Foo | 20 |
| Foo | 50 |
| Fo | 10 |
| Fo | 100 |
| F | 1000 |
+------+-------+
DESIRED OUTPUT:
+------+---------+--------+-------+
| Name | Surname | Salary | Bonus |
+------+---------+--------+-------+
| Foo | Bar | 100 | 80 |
| Foo | Kar | 300 | 0 |
| Fo | Ba | 35 | 110 |
+------+---------+--------+-------+
The closest I get is this:
SELECT
a.Name,
Surname,
sum(Salary),
sum(Bonus)
FROM (SELECT
Name,
Surname,
sum(Salary) as Salary
FROM input
GROUP BY 1,2) a LEFT JOIN (SELECT Name,
SUM(Bonus) as Bonus
FROM input2
GROUP BY 1) b
ON a.Name = b.Name
GROUP BY 1,2;
Which gives:
+------+---------+-------------+------------+
| Name | Surname | sum(Salary) | sum(Bonus) |
+------+---------+-------------+------------+
| Fo | Ba | 35 | 110 |
| Foo | Bar | 100 | 80 |
| Foo | Kar | 300 | 80 |
+------+---------+-------------+------------+
I can't figure out how to get rid of Bonus duplication. Ideal solution for me would be as specified in the 'DESIRED OUTPUT', which is adding Bonus to only one Name and for other records with the same Name adding 0.
You can use row_number():
select l.*, (case when l.seqnum = 1 then r.bonus else 0 end) as bonus
from (select l.*, row_number() over (partition by name order by salary) as seqnum
from "left" l
) l left join
(select r.name, sum(bonus) as bonus
from "right" r
group by r.name
) r
on r.name = l.name
Try a Row_number over the Name category partioned by Name. This will give you different numbers for your duplicates. You can then search for the case when this number is 1 and return the result you want. Else return 0. The code can look something like this.
SELECT
a.Name,
Surname,
sum(Salary),
Case when Duplicate_Order = 1
then bonus
else 0
end as 'Bonus'
FROM (SELECT
Name,
Surname,
sum(Salary) as Salary
,ROW_NUMBER() over (partition by Name order by name) as [Duplicate_Order]
FROM input
GROUP BY 1,2) a
LEFT JOIN (SELECT Name,
SUM(Bonus) as Bonus
FROM input2
GROUP BY 1) b
ON a.Name = b.Name
GROUP BY 1,2;
Hope that helps!
You can use Correlated Subquery with sum() aggregation to compute the bonus column, and then apply lag() window analytic function to get the zeros for successively identical valued column values for the name column :
select Name, Surname, Salary,
bonus - lag(bonus::int,1,0) over (partition by name order by salary) as bonus
from
(
select i1.*,
( select sum(Bonus)
from input2 i2
where i1.Name = i2.Name
group by i2.Name ) as bonus
from input i1
) ii
order by name desc, surname;
Demo

how to select records with specific value/condition in another table

I have 3 tables STUDENT, SUBJECT and GRADE.
STUDENT
+------------+------------+------------+
| student_id | first_name | last_name |
+------------+------------+------------+
| 0 | Arthur | Pain |
| 1 | Richard | Gordon |
| 2 | Jennifer | Adelaide |
+------------+------------+------------+
SUBJECT
+------------+--------------+
| subject_id | subject_name |
+------------+--------------+
| 001 | Math |
| 002 | Science |
| 003 | English |
+------------+--------------+
GRADE
+-------------+-------------+--------+
| student_id | subject_id | grade |
+-------------+-------------+--------+
| 0 | 001 | A |
| 0 | 002 | B |
| 0 | 003 | A |
| 1 | 001 | B |
| 1 | 002 | A |
| 1 | 003 | F |
| 2 | 001 | A |
| 2 | 002 | B |
| 2 | 003 | B |
+-------------+-------------+--------+
I have tried the query below.
SELECT * FROM student st WHERE EXISTS
(SELECT 1 FROM grade g WHERE st.student_id = g.student_id AND g.grade IN ('A','B');
I want to select the students with grades only 'A' OR 'B'.
What about simply adding a NOT EXISTS:
SELECT *
FROM student st
WHERE EXISTS (SELECT NULL
FROM grade g
WHERE st.student_id = g.student_id
AND g.grade IN ('A','B'))
AND NOT EXISTS (SELECT NULL
FROM grade g
WHERE st.student_id = g.student_id
AND g.grade IN ('C', 'D', 'F'))
Try :
SELECT *
FROM student s
WHERE NOT EXISTS
(SELECT 1
FROM grade g
WHERE g.student_id = s.student_id AND grade NOT IN ('A', 'B'));
You can use the joins to connects the multiple tables
select s.*,g.* from student as s
inner join GRADE as g on g.student_id=s.student_id
inner join SUBJECT as sj on sj.subject_id = g.subject_id
where g.grade in ('A','B')
i hope this will help
SELECT * FROM STUDENT
LEFT JOIN GRADE ON STUDENT.STUDENT_ID = GRADE.STUDENT_ID
LEFT JOIN SUBJECT ON GRADE.SUBJECT_ID = SUBJECT.SUBJECT_ID
WHERE GRADE.GRADE IN ('A','B')
Using Not In :
SELECT * FROM student st WHERE student_id not in
(SELECT student_id FROM grade g WHERE grade = 'F');
SELECT *
FROM STUDENT
WHERE student_id IN ( SELECT student_id
FROM GRADE
WHERE grade IN ( 'A', 'B' ) );
Alternatively:
SELECT DISTINCT student_id, first_name, last_name
FROM STUDENT NATURAL JOIN GRADE
WHERE grade IN ( 'A', 'B' );

Sql two table query most duplicated foreign key

I got those two tables sport and student:
First table sport:
|idsport | name |
_______________________
| 1 | bobsled |
| 2 | skating |
| 3 | boarding |
| 4 | iceskating |
| 5 | skiing |
Second table student:
foreign key
|idstudent | name | sport_idsport
__________________________________________
| 1 | john | 3 |
| 2 | pauly | 2 |
| 3 | max | 1 |
| 4 | jane | 2 |
| 5 | nico | 5 |
so far i did this it output which number is mostly inserted, but cant get it to work
with two tables
SELECT sport_idsport
FROM (SELECT sport_idsport FROM student GROUP BY sport_idsport ORDER BY COUNT(*) desc)
WHERE ROWNUM<=1;
I need to output name of most popular sport, in that case it would be skating.
I use oracle sql.
with counter as (
Select sport_idsport,
count(*) as cnt,
dense_rank() over (order by count(*) desc) as rn
from student
group by sport_idsport
)
select s.*, c.cnt
from sport s
join counter c on c.sport_idsport = s.idsport and c.rn = 1;
SQLFiddle example: http://sqlfiddle.com/#!4/b76e21/1
select cnt, sport_idsport from (
select count(*) cnt, sport_idsport
from student
group by sport_idsport
order by count(*) desc
)
where rownum = 1