Counting the unique values after group by clause - sql

I need to count students in every major for an academic year. There are three terms in a year. One student declares a different major in every terms . I need to take the last major he/she declared and count all the students in a major. So only one student for one major.
When I do group by by major, I can't avoid the duplicates.
I have only one table. It has everything I need.
I wrote this code. And It gives me the duplicated count.
SELECT MAJR_CODE, MAJR_DESC, COUNT(DISTINCT ID_KEY)
FROM STUDENT_ENROLLMENT
WHERE TERM in ('201830','201910','201920')
and REGISTERED='Y'
GROUP BY MAJR_CODE, MAJR_DESC
ORDER BY MAJR_CODE
How can I get the result I want?

You can use window functions to get the data for the most recent term:
SELECT MAJR_CODE, MAJR_DESC, COUNT(*)
FROM (SELECT se.*, ROW_NUMBER() OVER (PARTITION BY ID_KEY DESC ORDER BY TERM DESC) as seqnum
FROM STUDENT_ENROLLMENT se
WHERE TERM in ('201830', '201910', '201920') AND
REGISTERED = 'Y'
) se
WHERE seqnum = 1
GROUP BY MAJR_CODE, MAJR_DESC
ORDER BY MAJR_CODE

Related

SQL Select all MIN Values for each group

So I need to select all the students, having the minimum grade for each prof. For example, if Augustinus had two students with grade 1.0, then I would like to see both in the result.
Table of my data
What the result could look like, if the LIMIT was set to 10
So what I basically want is to see the best students that each prof has.
What I have tried is the following:
SELECT professor, student, min(note)
FROM temp
GROUP BY professor
ORDER BY note
The problem of course being that I only get one minimum value for each prof and not all minimum values.
*temp is just the table name
One way to solve these types of problems is to use a subquery to rank the grades for each class in a descending order. This involves a window function. With a second query you can limit the results based on your criteria of 10.
SELECT professor, student, note
FROM
(
SELECT professor,student,note,
row_number() over(partition by professor order by note desc) as downwardrank
) as rankings
WHERE
downwardrank <= 10
Just found a solution myself:
SELECT professor, student, note
FROM temp
WHERE (professor, note) IN
(SELECT professor, min(note)
FROM temp
GROUP BY professor
ORDER BY note)
ORDER BY note, professor, student
LIMIT 10

Find list of topper across each class when given individual scores for each subject

I need help in writing an efficient query to find a list of toppers (students with maximum total marks in each class) when we are given individual scores for each subject across different classes. We are required to return 3 columns: class, topper_student name and topper_student_total marks.
I have used multiple sub-queries to find a solution. I am sure there would be much better implementations available for this problem (maybe via joins or window functions?).
Input table and my solution can be found at SQL Fiddle link.
http://www.sqlfiddle.com/#!15/2919e/1/0
Input table:
It would be clearer to use temporary tables to store results along the way and make the result traceable, but the solution can be achieved with a single query:
WITH student_marks AS (
SELECT Class_num, Name, SUM(Marks) AS student_total_marks
FROM School
GROUP BY Class_num, Name
)
SELECT Class_num, Name, student_total_marks
FROM (
SELECT Class_num, Name, student_total_marks, ROW_NUMBER() OVER(partition by Class_num order by student_total_marks desc, Class_num) AS beststudentfirst
FROM student_marks
) A
WHERE A.beststudentfirst = 1
The query within WITH statement calculate a sum of marks for every student in a class. At this point, subject is not required anymore. The result is temporarily stored into student_marks.
Next, we need to create a counter (beststudentfirst) using ROW_NUMBER to number the total marks from the highest to the lowest in each class (order by student_total_marks desc, Class_num). The counter should be reinitiated each time the class changes (partition by Class_num order).
From this last result, we only need the counter (beststudentfirst) with the value of one. It is the top student in each class.
Window functions are the most natural way to approach this. If you always want exactly three students, then use row_number():
select Class_num, Name, total_marks
from (select name, class_num, sum(marks) as total_marks,
row_number() over (partition by class_num order by sum(marks) desc) as seqnum
from School
group by Class_num, Name
) s
where seqnum <= 1
order by class_num, total_marks desc;
If you want to take ties into account, then use rank() or dense_rank().
Here is the SQL Fiddle.
select Class_num,[Name],total_marks from
(
select Row_number() over (partition by class_num order by Class_num,SUM(Marks) desc) as
[RN],Class_num,[Name],SUM(Marks) as total_marks
from School
group by Class_num,[Name]
)A
where RN=1

SQL plus, top 3 rank across two tables

I'm trying to find a way to query the top three users in a database in terms of number of listens and output their user ID and their rank.
The schema for the two tables in question is as follows :
User(user_id, email, first_name, last_name, password, created_on, last_sign_in)
PreviouslyPlayed(user_id, track_id, timestamp)
I could see how many people pull this off with a count query, but am wondering is there's a way to do this with a rank or dense rank
If you just want the user id and are using Oracle 12g+, then you can do:
select pp.user_id, rank() over (order by count(*) desc) as therank
from previouslyplayed pp
group by pp.user_id
order by count(*) desc
fetch first 3 rows only;
In earlier versions, you would use a subquery:
select pp.*
from (select pp.user_id, rank() over (order by count(*) desc) as therank
from previouslyplayed pp
group by pp.user_id
) pp
where therank <= 3;
You might want to review row_number(), rank(), and dense_rank() to be sure you are getting what you really want (the difference is in how they handle ties).
You only need the join if you are concerned that something called user_id in one table is not a valid user id. That seems unlikely, in any well-designed database.

Trying to figure out how to join these queries

I have a table named grades. A column named Students, Practical, Written. I am trying to figure out the top 5 students by total score on the test. Here are the queries that I have not sure how to join them correctly. I am using oracle 11g.
This get's me the total sums from each student:
SELECT Student, Practical, Written, (Practical+Written) AS SumColumn
FROM Grades;
This gets the top 5 students:
SELECT Student
FROM ( SELECT Student,
, DENSE_RANK() OVER (ORDER BY Score DESC) as Score_dr
FROM Grades )
WHERE Student_dr <= 5
order by Student_dr;
The approach I prefer is data-centric, rather than row-position centric:
SELECT g.Student, g.Practical, g.Written, (g.Practical+g.Written) AS SumColumn
FROM Grades g
LEFT JOIN Grades g2 on g2.Practical+g2.Written > g.Practical+g.Written
GROUP BY g.Student, g.Practical, g.Written, (g.Practical+g.Written) AS SumColumn
HAVING COUNT(*) < 5
ORDER BY g.Practical+g.Written DESC
This works by joining with all students that have greater scores, then using a HAVING clause to filter out those that have less than 5 with a greater score - giving you the top 5.
The left join is needed to return the top scorer(s), which have no other students with greater scores to join to.
Ties are all returned, leading to more than 5 rows in the case of a tie for 5th.
By not using row position logic, which varies from darabase to database, this query is also completely portable.
Note that the ORDER BY is optional.
With Oracle's PLSQL you can do:
SELECT score.Student, Practical, Written, (Practical+Written) as SumColumn
FROM ( SELECT Student, DENSE_RANK() OVER (ORDER BY Score DESC) as Score_dr
FROM VOTES ) as score, students
WHERE score.score_dr <= 5
and score.Student = students.Student
order by score.Score_dr;
You can easily include the projection of the first query in the sub-query of the second.
SELECT Student
, Practical
, Written
, tot_score
FROM (
SELECT Student
, Practical
, Written
, (Practical+Written) AS tot_score
, DENSE_RANK() OVER (ORDER BY (Practical+Written) DESC) as Score_dr
FROM Grades
)
WHERE Student_dr <= 5
order by Student_dr;
One virtue of analytic functions is that we can just use them in any query. This distinguishes them from aggregate functions, where we need to include all non-aggregate columns in the GROUP BY clause (at least with Oracle).

SQL - Select top 1 with according to values from two columns

I know the title doesn't say much, but let me explain you my situation:
I have the following table:
Now, I would like to select top 1 from each department, but I don't want to get duplicate position id, so I want the top employee from each department by number of projects, but distinct position ids. The results are the highlighted rows.
You cannot guarantee that the returned positions will be the best. One position might be the best in two departments, in which case, one of the results constraints will need to be relaxed.
So, here is a method to get some (perhaps all) departments with the highest ranking but distinct positions. Start by choosing only the highest ranked employees for each department. These are the one with the most projects.
Then, for each PositionTypeId choose a random department from among these alternatives. Then, for each department, choose a random position type. The following query takes this approach:
select DepID, EmplyeeID, PositionTypeId, NumProjects
from (select t.*, row_number() over (partition by DepId order by newid()) as seqnum
from (select t.*, row_number() over (partition by PositionTypeId order by newid()) as position_seqnum
from (select t.*,
dense_rank() over (partition by DepId order by NumProducts desc
) as rank_seqnum
from t
) t
where rank_seqnum = 1
) t
where position_seqnum = 1
) t
where seqnum = 1;
This is not guaranteed to return a row for each department. But, it is guaranteed that all departments returned will have different position types and the rows will be best for that department. You could probably work to tweak the middle step to ensure a greater coverage of departments. However, because the problem is not guaranteed to have a solution, such tweaks may be more effort than they are worth.