Sql query for Top N rows using a group by - sql

Hi i tried many ways to solve this but missing some thing..
I have two tables Student and Score
Sid Cid Score
6 1 90
1 1 80
4 1 80
3 1 70
2 1 60
6 2 80
1 2 70
2 2 60
4 2 60
5 2 50
4 3 80
7 3 80
6 3 70
8 3 60
2 3 50
Sid Sname Sbday Ssex
1 As 1980 female
2 Al 1986 male
3 An 1989 male
4 ja 1986 male
5 ma 1983 female
6 phi 1986 male
7 Geo 1993 male
8 lil 1990 female
9 cha 1993 male
I need to Return Sid and Sname of the students who have the top 2 highest score for each course.
If existed, return Sid and Sname of the student who has the highest score among all the male students for each course.
Here top 2 highest score is not just top two records in a group for ex : top 2 highest score in 1st group is 90, 80 ,80 .
I need out put like this
Sid Cid Score
6 1 90
1 1 80
4 1 80
6 2 80
1 2 70
2 2 60
4 2 60
4 3 80
7 3 80
6 3 70
I tried the following code :
select A.Sid , S.SNAME, Score,Cid
from Score a,STUDENTS S
where 2 >(select count(Cid)
from Score
where Cid=a.Cid
and Score>a.Score)
AND A.SID = S.SID
order by Cid,Score desc

For the first question (list the students who have the top 2 highest score for each course), you should try this:
SELECT SC.Sid , ST.SNAME, SC.Score, SC.Cid
FROM ( SELECT *, DENSE_RANK() OVER(PARTITION BY Cid ORDER BY Score DESC) TopScore
FROM Score) AS SC
INNER JOIN Students AS ST
ON SC.Sid = ST.Sid
WHERE SC.TopScore <= 2
For your second quetion (the student who has the highest score among all the male students for each course), do the following:
SELECT A.Sid , A.SNAME, A.Score, A.Cid
FROM ( SELECT SC.Sid , ST.SNAME, SC.Score, SC.Cid, DENSE_RANK() OVER(PARTITION BY Cid ORDER BY Score DESC) TopScore
FROM Score AS SC
INNER JOIN Students AS ST
ON SC.Sid = ST.Sid
WHERE ST.Ssex = 'male') A
WHERE A.TopScore = 1
Hope it helps.

Related

How to select values from table based on field with the same id?

Let's say I have following sql tables:
Table "student"
id
first_name
last_name
1
Lillian
Nees
2
William
Lorenz
3
Mary
Moore
4
Giselle
Collins
5
James
Moultrie
6
John
Rodriguez
Table "exam_result":
exam_result_id
subject_id
student_id
mark
1
2
1
49
2
2
2
21
3
1
3
81
4
4
1
33
5
4
2
19
6
3
2
46
7
1
5
55
8
3
5
75
9
2
5
60
11
1
6
86
12
2
6
92
13
3
6
48
14
4
6
78
I need to select all students, who have all their exam marks <50 or haven't taken any exam at all.
So, in this case I need students with id 1, 2 and 4.
Closest thing I came up with is the following query, but it gives students with id 1, 2, 4 and 6. I don't need a student with id 6, since he has only 1 failed exam, not all of them.
SELECT DISTINCT s.id, s.first_name, s.last_name
FROM university.student s
LEFT JOIN
university.exam_result er ON
s.id = er.student_id
WHERE er.mark < 50 OR er.mark IS NULL;
I need it to work in both PostgreSQL and MariaDB, so I don't want anything specific to them.
The query that works is:
SELECT DISTINCT s.id, s.first_name, s.last_name
FROM university.student s
WHERE NOT EXISTS(SELECT 1 FROM university.exam_result er WHERE s.id = er.student_id AND er.mark > 49);
Thanks, #jarlh for your comment.
I would use a NOT EXISTS condition combined with a check for the marks
select s.*
from student s
where not exists (select *
from exam_result er
where er.student_id = s.id)
or 50 >= all (select er.mark
from exam_result er
where er.student_id = s.id)
One option is doing this with aggregation, by ensuring the count of marks less than 50 is equal to all the count of their marks. When the student has 0 marks, the condition will still be satisfied.
SELECT s.id, s.first_name, s.last_name
FROM student s
LEFT JOIN exam_result er ON s.id = er.student_id
GROUP BY s.id, s.first_name, s.last_name
HAVING COUNT(CASE WHEN er.mark < 50 THEN 1 END) = COUNT(er.mark)
Check the MariaDB demo and PostgreSQL demo.

Sum if same ID1 and ID2 - only once - SQL

I have the following data on SQL
EntryID
PersonID
JobID
JobSalary
1
1
1
270000
2
1
2
500000
3
2
3
320000
4
3
4
180000
5
2
3
320000
6
1
3
85000
7
1
1
270000
8
1
2
500000
9
2
3
320000
10
3
4
180000
For each entry, I want to have a column that calculates the total salary (of all jobs) of the specific person.
The tricky part is that multiple entries can refer to the same person and/or the same job, but I only want to sum up each job for each person once.
The output would be:
EntryID
PersonID
JobID
JobSalary
PersonTotalSalaryAllJobs
1
1
1
270000
855000
2
1
2
500000
855000
3
2
3
320000
320000
4
3
4
180000
180000
5
2
3
320000
320000
6
1
3
85000
855000
7
1
1
270000
855000
8
1
2
500000
855000
9
2
3
320000
320000
10
3
4
180000
180000
Any ideas on how to do this?
Thanks!
To get what you want you can use a query like this
SELECT
s.*,
pjts.total_salary
FROM salary s
LEFT JOIN (
SELECT
*,
SUM(jobsalary) OVER (PARTITION BY personid) AS total_salary
FROM (
SELECT DISTINCT
personid,
jobid,
jobsalary
FROM salary
) pjs
) pjts ON s.personid = pjts.personid AND s.jobid = pjts.jobid
You can check a working demo here
Or even simplier using only one subquery like this
SELECT
s.*,
pjts.total_salary
FROM salary s
LEFT JOIN (
SELECT
personid,
jobid,
SUM(jobsalary) OVER (PARTITION BY personid) AS total_salary
FROM salary
GROUP BY personid, jobid, jobsalary
) pjts ON s.personid = pjts.personid AND s.jobid = pjts.jobid
You can check a working demo here

Delete Group of Records based on Group Statistic

I have the following two example tables
Teams
Team ID
Team Name
1
Bears
2
Tigers
3
Lions
4
Sharks
Players
Player ID
Name
Team ID
Playtime
1
John
1
5
2
Adam
1
4
3
Smith
1
5
4
Michelle
2
5
5
Stephanie
2
10
6
David
2
10
7
Courtney
2
2
8
Frank
2
7
9
Teresa
2
1
10
Michael
3
3
11
May
4
1
12
Daniel
4
1
13
Lisa
4
4
I need a select statement with the following criteria
Select all teams with less than 4 players
Figure out the total playtime for all players on those teams and sort the resulting table based on this in descending order
Based on the example given, I would want a table that looks like this:
Teams
Team Name
Num Players
Total Playtime
Bears
3
14
Sharks
3
6
Lions
1
3
Finally, I want to cut all even rows from the previous table, so the final table would look like:
Team Name
Num Players
Total Playtime
Bears
3
14
Lions
1
3
You may try the following:
Query #1
SELECT
t."Team Name",
COUNT(p."Player ID") as "Num Players",
SUM(p."Playtime") as "Total Playtime"
FROM
teams t
LEFT JOIN
players p ON t."Team ID"=p."Team ID"
GROUP BY
t."Team Name"
HAVING
COUNT(p."Player ID") < 4
ORDER BY
SUM(p."Playtime") DESC;
Team Name
Num Players
Total Playtime
Bears
3
14
Sharks
3
6
Lions
1
3
Query #2
SELECT
t1."Team Name",
t1."Num Players",
t1."Total Playtime"
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY SUM(p."Playtime") DESC) as rn,
t."Team Name",
COUNT(p."Player ID") as "Num Players",
SUM(p."Playtime") as "Total Playtime"
FROM
teams t
LEFT JOIN
players p ON t."Team ID"=p."Team ID"
GROUP BY
t."Team Name"
HAVING
COUNT(p."Player ID") < 4
) t1
WHERE MOD(rn,2)=1
ORDER BY rn;
Team Name
Num Players
Total Playtime
Bears
3
14
Lions
1
3
View on DB Fiddle
Let me know if this works for you.

Counting current and longest streaks of a given value

I have 2 tables Person (ID, NAME, CLAN_ID) and DailyScore (PERSON_ID, CLAN_ID, DAY_NUMBER, SCORE).
SCORE can take the values "A", "B", "C" or "-" ("-" means absent).
I need to make 2 separate queries to get, for a given CLAN_ID:
the current streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
the longest ever streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
An important constraint is that "-" SCORES are ignored, as they represent absences, not real Scores.
Example data:
Table Person:
_ID NAME CLAN_ID
1 John 11
2 Alice 11
3 Bob 12
4 Sara 12
Table DailyScore:
PERSON_ID CLAN_ID DAY_NUMBER SCORE
1 11 1 A
1 11 2 A
1 11 3 A
1 11 4 C
1 11 5 A
2 11 1 B
2 11 2 C
2 11 3 B
2 11 4 A
2 11 5 A
3 12 1 A
3 12 2 A
3 12 3 A
3 12 4 A
3 12 5 B
4 12 1 C
4 12 2 B
4 12 3 C
4 12 4 A
4 12 5 -
Desired result example 1 (CLAN_ID=11, SCORE=A):
Current streak:
Alice 2
John 1
Longest ever streak:
John 3
Alice 2
Desired result example 2 (CLAN_ID=12, SCORE=A):
Current streak:
Sara 1*
Bob 0
*since "-" are ignored, Sara has a current streak of 1 A score
Longest ever streak:
Bob 4
Sara 1
Edit:
In case it helps, here's this example in SQL Fiddle: http://sqlfiddle.com/#!7/2ed69/2
The first query can be:
select
id, name, max(s) as streak
from (
select
p.id,
p.name,
count(*) over(partition by p.id order by s.day_number desc) as c,
sum(case when s.score = 'A' then 1 else 0 end)
over(partition by p.id order by s.day_number desc) as s
from person p
join dailyscore s on s.person_id = p.id
) x
where c = s
group by id, name

getting count from multiple tables on the basis of gender and scored percentages

I have to use 3 tables
1. tbl_school_info
school_id school_name
1 St. Joseph
2 White Cross
2. tbl_student_info
student_id school_id student_name student_gender
1 1 maria liejal F
2 1 eerika carthy F
3 1 heron M
4 2 Glenn hui M
5 2 joseph M
6 2 christii F
7 2 hina moggy F
3. tbl_student_marks
marks_id school_id student_id scored_percentage
1 1 1 78
2 1 2 79
3 1 3 20
4 2 4 65
5 2 5 78
6 2 6 84
7 2 7 83
The result I need is the male, female and total student count in each school, male female passed student count and highest percentage scored male female students. The result will be like this ::
school_name || male_stud_cnt || female_stud_cnt || passed_male_cnt || passed_female_cnt || top_percentage_male ||top_percentage_female
St. Joseph 1 2 0 2 20 79
White Cross 2 2 2 2 78 84
The students whose score is below 35% has failed in the exam. How can I write query to get this result ? Is it possible to get such result using SQL query in MS SQL Server? I am unable to get the counts, how can I write such query ?
You can try using condtional aggregation with case when expression
with cte as
(
select school_name,a.student_id,student_gender,scored_percentage from
tbl_student_marks a inner join tbl_student_info b
on a.student_id=b.student_id
inner join tbl_school_info c on a.school_id=b.school_id
)
select school_name,
count(case when student_gender='M' then student_id end) as male_stud_cnt,
count(case when student_gender='F' then student_id end) as female_stud_cnt,
count(case when student_gender='M' and scored_percentage>35 then student_id end) as passed_male_cnt,
count(case when student_gender='F' and scored_percentage>35 then student_id end) as passed_female_cnt,
max(case when student_gender='M' then scored_percentage end) as top_percentage_male,
max(case when student_gender='F' then scored_percentage end) as top_percentage_female
from cte
group by school_name