SQL display MIN() column with other columns displayed - sql

I am working on a report where I need to display a student id, the score and the lowest date/time that the score was filed. The table will have multiple scores per student across multiple days.
The problem I am having (using the sample code below) is that I am getting a new row for every score, instead of just one line per student showing the student id, score and date.
sample source table columns
student_id |
score |
score_date
select tests.student_id
,tests.score
,min(score_date)
from tests
group by tests.student_id
,tests.score

You use row_number() for this one.
select * from (
select t1.student_id
,t1.score
,row_number() over (partition by t1.student_id order by t1.score_date) rn
,t1.score_date
from tests t1) t
where t.rn = 1
There is also one trick by using correlated subquery.
select student_id
, min(score_date) score_date
, (select top 1 score from tests where score_date = min(t.score_date) and student_id = t.student_id) as score
from tests t
group by student_id
or if you are using sql server 2012 and higher versions. You can maximize the use of first_value() function.
select distinct first_value(student_id) over (partition by student_id order by score_date)
, first_value(score_date) over (partition by student_id order by score_date)
, first_value(score) over (partition by student_id order by score_date)
from tests t
group by student_id

Try this.
SELECT student_id, score, score_date
FROM tests T
INNER JOIN (SELECT student_id, Min(score_date) AS score_date FROM tests) X
ON T.student_id = X.student_id AND T.score_date = X.score_date

If you want all the dates per student when they had their lowest score?
Then the window function DENSE_RANK comes to mind. Sorted by ascending score.
WITH CTE_STUDENT_SCORES AS
(
SELECT student_id, score, score_date
, DENSE_RANK() OVER (PARTITION BY student_id ORDER BY score ASC) AS StudentAscendingScoreRank
FROM tests
)
SELECT student_id, score, score_date
FROM CTE_STUDENT_SCORES
WHERE StudentAscendingScoreRank = 1
ORDER BY student_id, score_date
If only the latest score_date with the student lowest score is needed?
Then one could limit on ROW_NUMBER instead.
Sorted by ascending score and descending score_date.
WITH CTE_STUDENT_SCORES AS
(
SELECT student_id, score, score_date
, ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY score ASC, score_date DESC) AS StudentAscendingScoreRownum
FROM tests
)
SELECT student_id, score, score_date
FROM CTE_STUDENT_SCORES
WHERE StudentAscendingScoreRownum = 1
ORDER BY score_date DESC, student_id;

Related

fetching a row from table

Having this data:
I am running this code:
SELECT student_info.student_name,student_info.department,student_info.marks,
rank() over (order by student_info.marks desc) score from student_info ;
and getting:
I want first two records alone with rank with out using rownum
Can you try this please, I think this is what you need
SELECT student_name, department, marks, score
FROM (SELECT s.*, RANK() OVER (ORDER BY marks DESC) AS score
FROM student_info s)
WHERE score = 1

Optimize a query for creating a ranking in MS SQL Server

I'm creating an application where users do workouts. They pass on their results via an app, and these results are stored in an SQL Server database. Results are saved in this way in a SQL Server table:
I want to write a query to create a ranking based on the best score of each user. This is what I have so far:
SELECT id,
workout_id,
level_id,
a.user_id,
total_time,
score,
datetime_added
FROM nodefit_rankings_fitness as a INNER JOIN
(
SELECT user_id,
MAX(score) AS MAXSCORE
FROM nodefit_rankings_fitness
GROUP BY user_id
) AS lookup
ON lookup.user_id = a.user_id
AND
lookup.MAXSCORE = a.score
ORDER BY score DESC,
datetime_added DESC
This generates this ranking:
The problem is that if a user has achieved the same maximum score a number of times, he will appear multiple times in the ranking. The query must be adjusted so that when a user has the same maximum score a few times, only the result of the last attempt (based on the datetime_added column) is displayed in the rankings.
Unfortunately, I cannot find a solution myself. Help is certainly appreciated.
If you care about performance, you should also try a correlated subquery:
SELECT id, workout_id, level_id, a.user_id, total_time, score, datetime_added
FROM nodefit_rankings_fitness nrf
WHERE nrf.id = (SELECT TOP (1) nrf2.id
FROM nodefit_rankings_fitness nrf2
WHERE nrf2.user_id = nrf.user_id
ORDER BY nrf2.score DESC
)
ORDER BY score DESC, datetime_added DESC;
In particular, this can take advantage of an index on nodefit_rankings_fitness(user_id, score desc, id).
Window functions make stuff like this easy. Something like:
SELECT id, workout_id, level_id, user_id, total_time, score, datetime_added
FROM (SELECT *, row_number() OVER (PARTITION BY user_id ORDER BY score DESC, datetime_added DESC) AS rn
FROM nodefit_rankings_fitness) AS a
WHERE rn = 1
ORDER BY score DESC, datetime_added DESC;

Sum column but for each row

How to do like
and I want to have result like
and what I already try is like this
select no, student, sum(point) from student group by no, student
But the result is not like what I expected.
You appear to want a "running sum" e.g.
select
no
, student
, sum(point) over(partition by student order by no) run_sum
from student
and it seems that the extra point might a subtraction of the 2nd last running sum from the final like this.
WITH cte
AS (
SELECT
no
, student
, SUM(point) OVER (PARTITION BY student ORDER BY no) run_sum
, ROW_NUMBER() OVER (PARTITION BY student ORDER BY no DESC) rn
FROM student)
SELECT
t.*
, coalesce(t.rum_sum,0) - coalesce(ex.run_sum,0) AS extra_point
FROM cte t
LEFT JOIN (
SELECT
*
FROM cte
WHERE rn = 2) ex ON t.student = ex.student
AND t.rn = 1
AND ex.rn = 2
;
but without more guidance on the required logic for extra_point that is just a guess.

Excluding only one MIN value on Oracle SQL

I am trying to select all but the lowest value in a column (GameScore), but when there are two of this lowest value, my code excludes both (I know why it does this, I just don't know exactly how to correct it and include one of the two lowest values).
The code looks something like this:
SELECT Id, SUM(Score) / COUNT(Score) AS Score
FROM
(SELECT Id, Score
FROM GameScore
WHERE Game_No = 1
AND Score NOT IN
(SELECT MIN(Score)
FROM GameScore
WHERE Game_No = 1
GROUP BY Id))
GROUP BY Id
So if I am drawing from 5 values, but one of the rows only pulls 3 scores because the bottom two are the same, how do I include the 4th? Thanks.
In order to do this you have to separate them up somehow; your current issue is that the 2 lowest scores are the same so any (in)equality operation performed on either values treats the other one identically.
You could use something like the analytic query ROW_NUMBER() to uniquely identify rows:
select id, sum(score) / count(score) as score
from ( select id, score, row_number() over (order by score) as score_rank
from gamescore
where gameno = 1
)
where score_rank <> 1
group by id
ROW_NUMBER():
assigns a unique number to each row to which it is applied (either each row in the partition or each row returned by the query), in the ordered sequence of rows specified in the order_by_clause, beginning with 1.
As the ORDER BY clause is on SCORE in ascending order one of the lowest score will be removed. This will be a random value unless you add other tie-breaker conditions to the ORDER BY.
You can do this a few ways, including what #Ben shows. From a mostly SQL Server background I was curious if just ROWNUM could be used and found this piece on ROWNUM vs ROW_NUMBER interesting. I'm not sure if it is dated.
All in a SQLFiddle.
Note: I'm using a subquery factoring/CTE as I think the read more clearly than in-line subqueries.
Using ROWNUM:
WITH OrderedScore AS (
SELECT id, game_no, score
,rownum as score_rank
FROM GameScore
WHERE game_no = 1
ORDER BY Score ASC
)
SELECT id
,sum(score)/count(score)
FROM OrderedScore
WHERE score_rank > 1
GROUP BY id;
Using ROW_NUMBER() OVER(ORDER BY...) as Ben does:
WITH OrderedScore AS (
SELECT id, game_no, score
,ROW_NUMBER() OVER(ORDER BY score ASC) as score_rank
FROM GameScore
WHERE game_no = 1
ORDER BY Score ASC
)
SELECT id
,sum(score)/count(score)
FROM OrderedScore
WHERE score_rank > 1
GROUP BY id;
Using ROW_NUMBER() OVER(PARTION BY...ORDER BY...) which I think leads to more flexibility if you want to remove the low score by game_no or id at some point:
WITH OrderedScore AS (
SELECT id, game_no, score
,ROW_NUMBER() OVER(PARTITION BY id ORDER BY score ASC) as score_rank
FROM GameScore
WHERE game_no = 1
ORDER BY Score ASC
)
SELECT id
,sum(score)/count(score)
FROM OrderedScore
WHERE score_rank > 1
GROUP BY id;

In Postgres, how can I average the latest 5 scores per user?

I am stuck on a query in Postgres and am getting pretty frustrated. I have a table called scores with three columns:
score, user_id and date
And I want to get the average of all users last 5 scores. This query doesn't exactly get me that:
SELECT user_id,
ROUND( AVG( score )::numeric, 2) as sc_avg
FROM
(SELECT ROW_NUMBER() OVER (PARTITION BY user_id) AS r,
sc.*
from mg.scores sc
WHERE score IS NOT NULL
ORDER BY date DESC) AS x
WHERE x.r >= 5
GROUP BY user_id;
Is there a better way to limit the last 5 jobs per user?
As a_horse commented, the ORDER BY clause has to go into the window function.
And since your order is descending, it needs to be <= instead of >=:
SELECT user_id, round(avg(score)::numeric, 2) AS sc_avg
FROM (
SELECT *
, row_number() OVER (PARTITION BY user_id ORDER BY date DESC) AS rn
FROM mg.scores
WHERE score IS NOT NULL
) AS x
WHERE x.rn <= 5
GROUP BY user_id;
If date can be NULL, use ORDER BY date DESC NULLS LAST. See:
PostgreSQL sort by datetime asc, null first?