I want to get the ranks/positions of students in a class based on their total scores.
However, my query keeps outputting the same rank to every student; giving all of them the 1st position.
Initially, I tried this query which worked very fine but it brings out all the students in a class.
SELECT `student_id`, rank, totscore
FROM (SELECT *, IF(#marks=(#marks:=totscore), #auto, #auto:=#auto+1) AS rank
FROM (SELECT * FROM
(SELECT `student_id`, SUM(`ft_tot_score`) AS totscore, `class_id`, `section_id`
FROM ftscores_primary,
(SELECT #auto:=0, #marks:=0) as init WHERE class_id = 8 and section_id = 2 and session_id = 17
GROUP BY `student_id`) sub ORDER BY totscore DESC)t) as result
This is the output:
student_id | rank | totscore
402 | 1 | 869
1314 | 2 | 849
1024 | 3 | 842
But I want something like this:
student_id | rank | totscore
1024 | 3 | 842
This is what I did
SELECT `student_id`, rank, totscore
FROM (SELECT *, IF(#marks=(#marks:=totscore), #auto, #auto:=#auto+1) AS rank
FROM (SELECT * FROM
(SELECT `student_id`, SUM(`ft_tot_score`) AS totscore, `class_id`, `section_id`
FROM ftscores_primary,
(SELECT #auto:=0, #marks:=0) as init WHERE class_id = 8 and section_id = 2 and session_id = 17 and student_id = 1024
GROUP BY `student_id`) sub ORDER BY totscore DESC)t) as result
Here is what I get
student_id | rank | totscore
1024 | 1 | 842
Modify your query as the following:
SELECT student_id, rank, totscore
FROM
(
SELECT student_id, totscore,
IF(#marks=(#marks:=totscore), #auto, #auto:=#auto+1) AS rank
FROM
(
SELECT student_id, SUM(ft_tot_score) AS totscore
FROM ftscores_primary, (SELECT #auto:=0, #marks:=0) as init
WHERE class_id = 8 and section_id = 2 and session_id = 17
GROUP BY student_id
) T
ORDER BY totscore DESC
) D
WHERE rank= #auto -- #auto holds the last rank value
See a demo.
Related
Here's what I'm trying to do. Let's say I have this table t:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
2 | 18 | 2012-05-19 | y
3 | 18 | 2012-08-09 | z
4 | 19 | 2009-06-01 | a
5 | 19 | 2011-04-03 | b
6 | 19 | 2011-10-25 | c
7 | 19 | 2012-08-09 | d
For each id, I want to select the row containing the minimum record_date. So I'd get:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
The only solutions I've seen to this problem assume that all record_date entries are distinct, but that is not this case in my data. Using a subquery and an inner join with two conditions would give me duplicate rows for some ids, which I don't want:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
5 | 19 | 2011-04-03 | b
4 | 19 | 2009-06-01 | a
How about something like:
SELECT mt.*
FROM MyTable mt INNER JOIN
(
SELECT id, MIN(record_date) AS MinDate
FROM MyTable
GROUP BY id
) t ON mt.id = t.id AND mt.record_date = t.MinDate
This gets the minimum date per ID, and then gets the values based on those values. The only time you would have duplicates is if there are duplicate minimum record_dates for the same ID.
I could get to your expected result just by doing this in mysql:
SELECT id, min(record_date), other_cols
FROM mytable
GROUP BY id
Does this work for you?
To get the cheapest product in each category, you use the MIN() function in a correlated subquery as follows:
SELECT categoryid,
productid,
productName,
unitprice
FROM products a WHERE unitprice = (
SELECT MIN(unitprice)
FROM products b
WHERE b.categoryid = a.categoryid)
The outer query scans all rows in the products table and returns the products that have unit prices match with the lowest price in each category returned by the correlated subquery.
I would like to add to some of the other answers here, if you don't need the first item but say the second number for example you can use rownumber in a subquery and base your result set off of that.
SELECT * FROM
(
SELECT
ROW_NUM() OVER (PARTITION BY Id ORDER BY record_date, other_cols) as rownum,
*
FROM products P
) INNER
WHERE rownum = 2
This also allows you to order off multiple columns in the subquery which may help if two record_dates have identical values. You can also partition off of multiple columns if needed by delimiting them with a comma
This does it simply:
select t2.id,t2.record_date,t2.other_cols
from (select ROW_NUMBER() over(partition by id order by record_date)as rownum,id,record_date,other_cols from MyTable)t2
where t2.rownum = 1
If record_date has no duplicates within a group:
think of it as of filtering. Simpliy get (WHERE) one (MIN(record_date)) row from the current group:
SELECT * FROM t t1 WHERE record_date = (
select MIN(record_date)
from t t2 where t2.group_id = t1.group_id)
If there could be 2+ min record_date within a group:
filter out non-min rows (see above)
then (AND) pick only one from the 2+ min record_date rows, within the given group_id. E.g. pick the one with the min unique key:
AND key_id = (select MIN(key_id)
from t t3 where t3.record_date = t1.record_date
and t3.group_id = t1.group_id)
so
key_id | group_id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
8 | 19 | 2009-06-01 | e
will select key_ids: #1 and #4
SELECT p.* FROM tbl p
INNER JOIN(
SELECT t.id, MIN(record_date) AS MinDate
FROM tbl t
GROUP BY t.id
) t ON p.id = t.id AND p.record_date = t.MinDate
GROUP BY p.id
This code eliminates duplicate record_date in case there are same ids with same record_date.
If you want duplicates, remove the last line GROUP BY p.id.
This a old question, but this can useful for someone
In my case i can't using a sub query because i have a big query and i need using min() on my result, if i use sub query the db need reexecute my big query. i'm using Mysql
select t.*
from (select m.*, #g := 0
from MyTable m --here i have a big query
order by id, record_date) t
where (1 = case when #g = 0 or #g <> id then 1 else 0 end )
and (#g := id) IS NOT NULL
Basically I ordered the result and then put a variable in order to get only the first record in each group.
The below query takes the first date for each work order (in a table of showing all status changes):
SELECT
WORKORDERNUM,
MIN(DATE)
FROM
WORKORDERS
WHERE
DATE >= to_date('2015-01-01','YYYY-MM-DD')
GROUP BY
WORKORDERNUM
select
department,
min_salary,
(select s1.last_name from staff s1 where s1.salary=s3.min_salary ) lastname
from
(select department, min (salary) min_salary from staff s2 group by s2.department) s3
There is a table which has 4 columns: id, student_name,phone_num, score.
And I want to select the TOP 3 student base on the score.
table:
id|student_name |phone_num|score
1 | James | 001350 | 89
2 | Roomi | 123012 | 78
3 | Sibay | 123012 | 65
4 | Ellae | 123012 | 78
5 | Katee | 123012 | 33
As the table shows, there are two students have the same scores.
So they are in the same rank.
I tried to use 'LIMIT' but it can only select 3 rows.
SELECT id,student_name,score
FROM table
GROUP BY id,student_name,score
ORDER BY score
LIMIT 3
Expected results:
id|student_name |score
1 | James | 89
2 | Roomi | 78
4 | Ellae | 78
3 | Sibay | 65
Thanks!
You'll want to use a ranking function - I'd recommend Dense Rank.
; with CTE as
(Select ID, Student_Name, Score, Dense_Rank() over (order by score desc) as DR
From Table)
Select *
from CTE
where DR <= 3
To expand on this function:
Dense_Rank will assign tied values the same number, then assign the next highest value the next highest number (in comparison to Rank, which will skip ranks if there are ties). For example:
Value Rank Dense_Rank
1 1 1
3 2 2
3 2 2
7 4 3
8 5 4
You want to use DENSE_RANK here:
WITH cte AS (
SELECT id, student_name, score,
DENSE_RANK() OVER (ORDER BY score DESC) dr
FROM yourTable
)
SELECT id, student_name, score
FROM cte
WHERE dr <= 3
ORDER BY score DESC;
Another way, using a subquery to find the top 3 distinct highest scores:
SELECT id, student_name, score
FROM yourTable
WHERE score IN (SELECT DISTINCT TOP 3 score FROM yourTable ORDER BY score DESC)
ORDER BY score DESC;
This second approach is similar to what you were trying to do. Here is a demo for the second query:
Demo
I have a table with players, results and ID:
Player | Result | ID
---------------
An | W | 1
An | W | 1
An | L | 0
An | W | 1
An | W | 1
An | W | 1
Ph | L | 0
Ph | W | 1
Ph | W | 1
Ph | L | 0
Ph | W | 1
A 'W' will always have an ID of 1,
I need to create a query that will count the maximum number of consecutive 'W's for each player:
Player | MaxWinStreak
---------------------
An | 3
Ph | 2
I tried to use Rows Unbounded Preceeding but i can only get it to count the maximum number of Ws in total, and not consecutively
Select
t2.player
,max(t2.cumulative_wins) As 'Max'
From
( Select
t.Player
,Sum(ID) Over (Partition By t.Result,t.player
Order By t.GameWeek Rows Unbounded Preceding) As cumulative_wins
From
t
) t2
Group By
t2.player
Is there a different approach i can take ?
You need a column to specify the ordering. SQL tables represent unordered sets. In the below query, the ? represents this column.
You can use the difference of row numbers to get each winning streak:
select player, count(*) as numwins
from (select t.*,
row_number() over (partition by player order by ?) as seqnum,
row_number() over (partition by player, result order by ?) as seqnum_r
from t
) t
where result = 'W'
group by player, (seqnum - seqnum_r);
You can then get the maximum:
select player, max(numwins)
from (select player, count(*) as numwins
from (select t.*,
row_number() over (partition by player order by ?) as seqnum,
row_number() over (partition by player, result order by ?) as seqnum_r
from t
) t
where result = 'W'
group by player, (seqnum - seqnum_r)
) pw
group by player;
I am trying to select top 2 records from a database table result that looks like this
SubjectId | StudentId | Levelid | total
------------------------------------------
1 | 1 | 1 | 89
1 | 2 | 1 | 77
1 | 3 | 1 | 61
2 | 4 | 1 | 60
2 | 5 | 1 | 55
2 | 6 | 1 | 45
i tried this query
SELECT rv.subjectid,
rv.total,
rv.Studentid,
rv.levelid
FROM ResultView rv
LEFT JOIN ResultView rv2
ON ( rv.subjectid = rv2.subjectid
AND
rv.total <= rv2.total )
GROUP BY rv.subjectid,
rv.total,
rv.Studentid
HAVING COUNT( * ) <= 2
order by rv.subjectid desc
but some subjects like where missing, i even tried the suggestiong frm the following link
How to select the first N rows of each group?
but i get more that two for each subjectid
what am i doing wrong?
You could use a correlated subquery:
select *
from ResultView rv1
where SubjectId || '-' || StudentId || '-' || LevelId in
(
select SubjectId || '-' || StudentId || '-' || LevelId
from ResultView rv2
where SubjectID = rv1.SubjectID
order by
total desc
limit 2
)
This query constructs a single-column primary key by concatenating three columns. If you have a real primary key (like ResultViewID) you can substitute that for SubjectId || '-' || StudentId || '-' || LevelId.
Example at SQL Fiddle.
I hope I'm understanding your question correctly. Let me know if this is correct:
I recreated your table:
CREATE TABLE stack (
SubjectId INTEGER(10),
StudentId INTEGER(10),
Levelid INTEGER(10),
total INTEGER(10)
)
;
Inserted values
INSERT INTO stack VALUES
(1,1,1,89),
(1,2,1,77),
(1,3,1,61),
(2,4,1,60),
(2,5,1,55),
(2,6,1,45)
;
If you're trying to get the top group by Levelid (orderd by total field, assuming StudentID as primary key):
SELECT *
FROM stack AS a
WHERE a.StudentID IN (
SELECT b.StudentID
FROM stack AS b
WHERE a.levelid = b.levelid
ORDER BY b.total DESC
LIMIT 2
)
;
Yields this result:
SubjectId | StudentId | Levelid | total
1 | 1 | 1 | 89
1 | 2 | 1 | 77
Example of top 2 by SubjectId, ordered by total:
SELECT *
FROM stack AS a
WHERE a.StudentID IN (
SELECT b.StudentID
FROM stack AS b
WHERE a.subjectID = b.subjectID
ORDER BY b.total DESC
LIMIT 2
)
;
Result:
SubjectId | StudentId | Levelid | total
1 | 1 | 1 | 89
1 | 2 | 1 | 77
2 | 4 | 1 | 60
2 | 5 | 1 | 55
I hope that was the answer you were looking for.
ROW_NUMBER window function
SQLite now supports window functions, so the exact same code that works for PostgreSQL at Grouped LIMIT in PostgreSQL: show the first N rows for each group? now also works for SQLite.
This could be potentially faster than the other answers so far as it does not run a correlated subquery.
Supposing you want to get the 2 highest total rows for each StudentID:
SELECT *
FROM (
SELECT
ROW_NUMBER() OVER (
PARTITION BY "StudentID"
ORDER BY "total" DESC
) AS "rnk",
*
FROM "mytable"
) sub
WHERE
"sub"."rnk" <= 2
ORDER BY
"sub"."StudentID" ASC,
"sub"."total" DESC
Tested on SQLite 3.34, PostgreSQL 14.3. GitHub upstream
I have a table holding weekly scores of players:
# select * from pref_money limit 5;
id | money | yw
----------------+-------+---------
OK32378280203 | -27 | 2010-44
OK274037315447 | -56 | 2010-44
OK19644992852 | 8 | 2010-44
OK21807961329 | 114 | 2010-44
FB1845091917 | 774 | 2010-44
(5 rows)
This SQL statement gets me the weekly winners and how many times each player has won:
# select x.id, count(x.id) from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money
) x
where x.ranking = 1 group by x.id;
id | count
------------------------+-------
OK9521784953 | 1
OK356310219480 | 1
MR797911753357391363 | 1
OK135366127143 | 1
OK314685454941 | 1
OK308121034308 | 1
OK4087658302 | 5
OK452217781481 | 6
....
I would like to save the latter number in the medals column of the players table:
# \d pref_users;
Table "public.pref_users"
Column | Type | Modifiers
------------+-----------------------------+--------------------
id | character varying(32) | not null
first_name | character varying(64) |
last_name | character varying(64) |
city | character varying(64) |
medals | integer | not null default 0
How to do this please? I can only think of using a temporary table, but there must be an easier way... Thank you
UPDATE:
The query suggested by Clodoaldo works, but now my cronjob occasionally fails with:
/* reset and then update medals count */
update pref_users set medals = 0;
psql:/home/afarber/bin/clean-database.sql:63: ERROR: deadlock detected
DETAIL: Process 31072 waits for ShareLock on transaction 124735679; blocked by process 30368.
Process 30368 waits for ShareLock on transaction 124735675; blocked by process 31072.
HINT: See server log for query details.
update pref_users u
set medals = s.medals
from (
select id, count(id) medals
from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money where yw <> to_char(CURRENT_TIMESTAMP, 'IYYY-IW')
) x
where ranking = 1
group by id
) s
where u.id = s.id;
update pref_users u
set medals = s.medals
from (
select id, count(id) medals
from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money
) x
where ranking = 1
group by id
) s
where u.id = s.id
You could create a view which uses your "medal-select" and joins it with the actual data:
CREATE VIEW pref_money_medals AS
SELECT *
FROM pref_money
JOIN (SELECT count(x.id)
FROM (SELECT id, row_number()
OVER(PARTITION BY yw ORDER BY money DESC) AS ranking
FROM pref_money
) x
WHERE x.ranking = 1 group by x.id) medals
ON pref_money.id = medals.id;