Mysql Group By Levels ranking - mysql

rank points player_id quiz_id
1 88 1 40
2 80 3 40
3 30 3 41
4 20 1 41
Getting this output from the following query:
SELECT m.rank,
m.scorer AS points,
m.player_id
FROM
( SELECT d.player_id,
d.scorer, #rownum := #rownum + 1 AS rank
FROM
( SELECT t.player_id,
SUM(t.score) AS scorer
FROM answers t
JOIN PROFILE ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1
AND quiz.contest_id = 1
AND profile.signin_source_id != 1
AND profile.is_active = 1
AND t.quiz_id IN (1,
2)
GROUP BY t.player_id
ORDER BY scorer DESC, t.created_utc ASC) d,
(SELECT #rownum := 0) r) m
WHERE m.scorer > 0
However, the output I want is rank for each level separated out.
rank points player_id quiz_id
1 88 1 40
2 80 3 40
1 30 3 41
2 20 1 41
I followed these :
How to perform grouped ranking in MySQL
https://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/
But can't get the desired output. Any suggestion or help is appreciated.

Try this query, it is simplier IMO:
select #quiz_id_lag := 0, #rank := 1;
select rank, points, player_id, quiz_id from (
select points,
player_id,
case when #quiz_id_lag = quiz_id then #rank := #rank + 1 else #rank := 1 end rank,
#quiz_id_lag,
#quiz_id_lag := quiz_id,
quiz_id
from tbl
order by quiz_id, points desc
) a;
To incorporate this in your query, try:
SELECT #quiz_id_lag := 0, #rank := 1;
SELECT rank,
scorer AS points,
player_id
FROM (
SELECT quiz_id,
player_id,
scorer,
CASE WHEN #quiz_id_lag = quiz_id THEN #rank := #rank + 1 ELSE #rank := 1 END rank,
#quiz_id_lag := quiz_id,
quiz_id
FROM (
SELECT t.player_id,
SUM(t.score) AS scorer,
t.quiz_id
FROM answers t
JOIN PROFILE ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1 AND quiz.contest_id = 1 AND profile.signin_source_id != 1
AND profile.is_active = 1 AND t.quiz_id IN (1, 2)
GROUP BY t.player_id
) d
WHERE scorer > 0
ORDER BY quiz_id, scorer DESC
) m

Finally got the desired result, ending up with the query, in order to get proper ranking for level wise:
SELECT m.rank,m.scorer AS points,m.player_id, m.quiz_id FROM (
SELECT d.player_id,d.scorer,
#rownum:= CASE WHEN #quiz_id <> d.quiz_id THEN 1 ELSE #rownum+1 END as rank,
#quiz_id:= d.quiz_id as quiz_id FROM
(SELECT #rownum := 1) r,
(SELECT #quiz_id := 0) c,(
SELECT t.player_id,SUM(t.score) as scorer, t.quiz_id
FROM answers t JOIN profile ON
profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1 AND quiz.contest_id = 2 AND
profile.signin_source_id != 1 AND profile.is_active = 1
GROUP BY t.player_id,t.quiz_id
ORDER BY quiz_id DESC,scorer DESC, t.created_utc ASC) d
) m
WHERE m.scorer > 0 ORDER BY quiz_id
This will give the entire result set for all the levels for a specific group, if want to get the rank for specific levels from a specific group, then do add
AND t.quiz_id IN (1,2)
Thanks to all who ever participated!

Try this
SELECT m.rank,m.scorer AS points,m.player_id, m.quiz_id
FROM (
SELECT d.player_id,d.quiz_id, d.scorer,
#cur:= IF(quiz_id=#id, #cur+1, 1) AS rank,
#id := quiz_id
FROM (
SELECT t.player_id, quiz.id as quiz_id, SUM(t.score) as scorer
FROM answers t JOIN profile ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quize.id
WHERE t.is_active = 1 AND quiz.contest_id = 1
AND profile.signin_source_id != 1 AND profile.is_active = 1
AND t.quiz_id IN (1,2)
GROUP BY t.player_id, quiz.id
ORDER BY scorer DESC
) d, (SELECT #id:=(SELECT MIN(id) FROM quiz), #cur:=0) AS init
order by d.quiz_id, d.scorer desc) m
WHERE m.scorer > 0

Related

How to get TopN query group by month MYSQL

There's a table like:
months contact COUNT
202007 asdas 45
202007 madhouse 1
202007 RORC YANG 1
202007 RORG 2
202007 ROR 5
202008 SARINA 1
202008 SMB 1
How can I get top 4 query result each month?
Expected result:
months contact COUNT
202007 asdas 45
202007 ROR 5
202007 RORG 2
202008 SARINA 1
202008 SMB 1
I'm working with mysql5.6
Here are 2 choices. The first uses rank() over() which does not guarantee only 4 rows per month (there could be more) and the second uses row_number() over() which will limit number of rows to a max of 4 per month
select
*
from (
select
* , rank() over(partition by months order by c desc) as cr
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
) as d
where cr <= 4
;
select
*
from (
select
* , row_number() over(partition by months order by c desc) as rn
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
) as d
where rn <= 4
;
see demo
for older MySQL try a row number hack:
select
*
from (
select
#row_num :=IF(#prev_value=g.months,#row_num+1,1)AS RowNumber
, g.months
, g.contact
, g.c
, #prev_value := g.months
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
CROSS JOIN (SELECT #row_num :=1, #prev_value :='') vars
ORDER BY g.months, g.contact
) as d
where RowNumber <= 4
see that in demo
TOP5
SELECT z.months, z.contact, z.count
FROM
(SELECT
x.*,
#rownum := #rownum + 1,
IF(#part = x.months,#r := #r + 1,#r := 1) AS rank,
#part := x.months
FROM
(
SELECT
*
FROM
my_table e
ORDER BY
e.months ASC,e.count DESC) X,
(
SELECT
#rownum := 0,
#part := NULL,
#r := 0) rt)z
WHERE z.rank <=5

SQL SUM TOP 5 values per category then SUM category totals per ID

I'm trying to produce an SQL statement to SUM top 5 values within categories per userID to create an overall total. Is this possible and how do I achieve it? I can sum the top 5 per single category or ALL but struggling to see how I can SUM each separate category total together.
For example,
ID Cater Weight
--------------------------------
1 Cheese 10
2 Bacon 15
1 Cheese 5
2 Bacon 10
1 Cheese 22
2 Cheese 5
1 Bacon 10
1 Cheese 10
2 Cheese 5
1 Cheese 20
2 Bacon 10
1 Cheese 30
The results i'm looking for is,
ID Total_Weight
-------------------
1 102 Top 5 Cheese (10+22+10+20+30) + Top 5 Bacon (10)
2 45 Top 5 Cheese (5+5) + Top 5 Bacon (15+10+10)
Any values outside of the Top 5 are ignored.
The code below displays the SUM of the top 5 weights from ALL categories AS total weight. Can I achieve what I want from a single statement?
$log = "SELECT id, cater,
SUM(weight) AS total_weight
FROM ( SELECT id,
CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1
ELSE #ROW_NUMBER := 1
END AS rn,
cater,
weight,
#id := id
FROM individual,
(SELECT #ROW_NUMBER := 1, #ID := '') r
ORDER
BY
id, weight DESC
) TMP
WHERE rn <= 5
AND cater <> ''
GROUP
BY id
ORDER
BY total_weight DESC";
There might have some other better solution. But this will provide your expected result-
SELECT B.id,
SUM(T_weight) Total_Weight,
group_concat(concat('TOP 5 ',B.cater,' (',B.T,')') SEPARATOR ' ') Details
FROM
(
SELECT ID,cater,SUM(Weight) T_weight,group_concat(weight SEPARATOR '+') T
FROM
(
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cheese' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Bacon' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat3' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat4' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat5' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat6' ORDER BY id, weight DESC
)A WHERE rn < 6
)A
GROUP BY ID,Cater
)B
group by id
The output is-
1 191 TOP 5 Cheese (10+22+20+10+30) TOP 5 Cat3 (25+9+20+16+13) TOP 5 Bacon (10)
2 45 TOP 5 Cheese (5+5) TOP 5 Bacon (15+10+10)
If you have MySql version 8 or higher, you can use this code:
SELECT id, SUM (weight)
FROM (SELECT test.*,
ROW_NUMBER ()
OVER (PARTITION BY id, categ ORDER BY weight DESC)
rn
FROM test) sub
WHERE sub.rn < 6
GROUP BY id;
You need MySql 8+ because I used the function row_number and only from version 8 it exists.
In the below fiddle example, I used Microsoft Sql Server 2017 because they didn't had MySql 8 or higher.
You can see your example here.

What is the equivalent of MySQL #currant := #currank + 1 in MS SQL?

This is the main MySQL script I try to convert this following script into MS SQL script. I don't know the equivalent.
SELECT
(SELECT
COALESCE(transaction_price, 0)
FROM
(SELECT
COALESCE(transaction_price, 0) AS TRANSACTION_PRICE, COALESCE(actual_size, 0) AS ACTUAL_SIZE, #currank = #currank + 1 AS rank
FROM
dw_property_detail p, (SELECT #currank:=0) r
WHERE
land_id = 2 AND transaction_price IS NOT NULL AND flat_type = 'Studio'
ORDER BY transaction_price DESC) x
WHERE
x.rank = 1) AS TRAN_S,
(SELECT
COALESCE(per_ft_s, 0)
FROM
(SELECT
COALESCE(transaction_price / actual_size, 0) AS PER_FT_S, #currank:=#currank + 1 AS rank
FROM
dw_property_detail p, (SELECT #currank:=0) r
WHERE
land_id = 2 AND transaction_price IS NOT NULL AND flat_type = 'Studio'
ORDER BY COALESCE(transaction_price / actual_size, 0) DESC) x
WHERE
x.rank = 1) AS PER_FT_S
What is the equivalent of #currank := #currank + 1 and SELECT #currank := 0?
ROW_NUMBER (window/analytic function - feature that currently is not supported by MySQL)
https://msdn.microsoft.com/en-us/library/ms186734.aspx
Add it to the SELECT clause as an additional column.
You won't need now the query's ORDER BY, for that purpose (but do keep it if you want ordered results)
row_number() over (order by transaction_price desc)
SELECT COALESCE(transaction_price, 0) AS TRANSACTION_PRICE
,COALESCE(actual_size, 0) AS ACTUAL_SIZE
,row_number () over (order by transaction_price DESC) AS rank
FROM dw_property_detail p
WHERE land_id = 2
AND flat_type = 'Studio'
AND transaction_price IS NOT NULL
ORDER BY transaction_price DESC

Select recent n number of entries of all users from table

I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;

COUNT() with nulls, inside subquery

I have a bit of a problem with an advanced query that I am struggling to get my head around.
Essentally there are votes in a votes table that correspond to a given soundtrack. My query needs to get a rank for a soundtrack based on the votes that it has been awarded.
My approach below works just fine when there are votes in the table but the rank is given a NULL value when there are none in there.
Here's the query:
SELECT soundtrack.*,
(SELECT WrappedQuery.rank
FROM (SELECT #rownum := #rownum + 1 AS rank,
prequery.soundtrack_id
FROM (SELECT #rownum := 0) sqlvars,
(SELECT Count(*),
soundtrack_id
FROM vote
GROUP BY vote.soundtrack_id
ORDER BY Count(*) DESC) prequery) WrappedQuery
WHERE WrappedQuery.soundtrack_id = soundtrack.id) AS rank
FROM soundtrack
WHERE soundtrack.id = 33
AND live = 1
ORDER BY rank ASC
I have a feeling the problem is to do with the (SELECT COUNT(*)) part, but everything I have tried so far isn't working out.
Hoping someone could shed some light on my issue.
EDIT
Here's the SQLFiddle
http://www.sqlfiddle.com/#!2/c8db2/2/0
THAT ONE IS GOOD:
SELECT soundtrack.*,
(SELECT WrappedQuery.rank
FROM (SELECT #rownum := #rownum + 1 AS rank,
prequery.soundtrack_id
FROM (SELECT #rownum := 0) sqlvars,
(
SELECT COALESCE(COUNT(vote.soundtrack_id),0) AS no_rows,
soundtrack.id AS soundtrack_id
FROM soundtrack
LEFT JOIN vote ON soundtrack.id=vote.soundtrack_id
GROUP BY soundtrack.id
ORDER BY 1 DESC
) prequery) WrappedQuery
WHERE WrappedQuery.soundtrack_id = soundtrack.id) AS rank
FROM soundtrack
ORDER BY rank ASC;
SEE: http://www.sqlfiddle.com/#!2/74698/2/0
I've had some luck ranking in my own work using the row_number function. But otherwise, the coalesce function might help you out.
SELECT soundtrack.*, rankquery.rank
FROM (
SELECT row_number() over(partition by prequery.soundtrack_id order by prequery.num_votes) as rank,
prequery.soundtrack_id
FROM (
SELECT COALESCE(COUNT(*),0) as num_votes, soundtrack_id
FROM vote
GROUP BY soundtrack_id
ORDER BY num_votes DESC
) prequery
) rankquery
INNER JOIN soundtrack
rankquery.soundtrack_id = soundtrack.id
WHERE soundtrack.id = 33
AND live = 1
ORDER BY rank
SELECT soundtrack.*, rankquery.rank
FROM(
SELECT prequery.*, #rownum := #rownum + 1 AS rank
(
SELECT COALESCE(Count(*),0) as num_votes,
soundtrack_id
FROM vote
GROUP BY soundtrack_id
ORDER BY num_votes DESC
) as prequery,
(SELECT #rownum := 0) as sqlvars
) rankquery
INNER JOIN soundtrack
rankquery.soundtrack_id = soundtrack.id
WHERE soundtrack.id = 33
AND soundtrack.live = 1
ORDER BY rankquery.rank ASC