I'm trying to get a users ranking getting his highest performances in every beatmap.
I get the user highest performance in every beatmap (only taking the top 5 performances) and adding them together, but it fails when the highest performance in one beatmap is repeated... because it counts twice
I'm based in this solution, but it doesn't works well for me...
Using MySQL 5.7
What i'm doing wrong?
Fiddle
Using this code:
SET group_concat_max_len := 1000000;
SELECT #i:=#i+1 rank, x.userID, x.totalperformance FROM (SELECT r.userID, SUM(r.performance) as totalperformance
FROM
(SELECT Rankings.*
FROM Rankings INNER JOIN (
SELECT userID, GROUP_CONCAT(performance ORDER BY performance DESC) grouped_performance
FROM Rankings
GROUP BY userID) group_max
ON Rankings.userID = group_max.userID
AND FIND_IN_SET(performance, grouped_performance) <= 5
ORDER BY
Rankings.userID, Rankings.performance DESC) as r
GROUP BY userID) x
JOIN
(SELECT #i:=0) vars
ORDER BY x.totalperformance DESC
Expected result:
+------+--------+------------------+
| rank | userID | totalperformance |
+------+--------+------------------+
| 1 | 1 | 450 |
+------+--------+------------------+
| 2 | 2 | 250 |
+------+--------+------------------+
| 3 | 5 | 140 |
+------+--------+------------------+
| 4 | 3 | 50 |
+------+--------+------------------+
| 5 | 75 | 10 |
+------+--------+------------------+
| 6 | 45 | 0 | --
+------+--------+------------------+
| 7 | 70 | 0 | ----> This order is not relevant
+------+--------+------------------+
| 8 | 76 | 0 | --
+------+--------+------------------+
Actual Result:
+------+--------+------------------+
| rank | userID | totalperformance |
+------+--------+------------------+
| 1 | 1 | 520 |
+------+--------+------------------+
| 2 | 2 | 350 |
+------+--------+------------------+
| 3 | 5 | 220 |
+------+--------+------------------+
| 4 | 3 | 100 |
+------+--------+------------------+
| 5 | 75 | 10 |
+------+--------+------------------+
| 6 | 45 | 0 | --
+------+--------+------------------+
| 7 | 70 | 0 | ----> This order is not relevant
+------+--------+------------------+
| 8 | 76 | 0 | --
+------+--------+------------------+
As you have mentioned that you are picking only top 5 performances per user across beatmaps then you can try this way:
select #i:=#i+1, userid,performance from (
select userid,sum(performance) as performance from (
select
#row_number := CASE WHEN #last_category <> t1.userID THEN 1 ELSE #row_number + 1 END AS row_number,
#last_category :=t1.userid,
t1.userid,
t1.beatmapid,
t1.performance
from (
select
userid, beatmapid,
max(performance) as performance
from Rankings
group by userid, beatmapid
) t1
CROSS JOIN (SELECT #row_number := 0, #last_category := null) t2
ORDER BY t1.userID , t1.performance desc
) t3
where row_number<=5
group by userid
)
t4 join (SELECT #i := 0 ) t5
order by performance desc
Above query will not consider duplicate Performance Score and pick only top 5 performance values.
DEMO
Related
This is example of my table :
+-----+-----+------------+--------+-------------+--------------+
| LID | AID | Created | TypeID | PaymentDate | PaymentValue |
+-----+-----+------------+--------+-------------+--------------+
| 1 | 529 | 2017-05-12 | 1 | 2017-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 2 | 529 | 2018-04-10 | 4 | 2018-04-10 | 200 |
+-----+-----+------------+--------+-------------+--------------+
| 3 | 441 | 2014-01-23 | 3 | 2014-01-23 | 300 |
+-----+-----+------------+--------+-------------+--------------+
| 4 | 324 | 2017-09-14 | 1 | 2017-09-14 | 400 |
+-----+-----+------------+--------+-------------+--------------+
| 5 | 111 | 2018-05-12 | 0 | 2018-05-12 | 340 |
+-----+-----+------------+--------+-------------+--------------+
| 6 | 529 | 2018-05-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 7 | 529 | 2018-06-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 8 | 529 | 2018-07-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 9 | 529 | 2018-08-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 10 | 529 | 2018-09-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 11 | 529 | 2018-01-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 12 | 529 | 2018-05-14 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 13 | 529 | 2018-05-21 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 14 | 529 | 2018-03-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
Here another table
+-----+-------+
| ID |caption|
+-----+-------+
| 0 | bad |
+-----+-------+
| 1 | good |
+-----+-------+
I need to get 10 latest records per AID. If there less than 10 records for some AID anyway i need to get ten rows and put "No payment date" into PaymentDate and Created fields, Null into TypeID and 0 into PaymentValue. I can get 10 or less latest records with
select *
from (select *,
(#rn := if(#c = AID, #rn + 1,
if(#c := AID, 1, 1)
)
) as rn
from history cross join
(select #rn := 0, #c := -1) params
order by AID, Created desc
) t
having rn <= 10;
But i dont know how force mysql to output 10 rows for each AID. Help me please.
Result should be in a form
AID,TypeId,Created,Caption
I have done it.
This query needs to create a row of 10 records to combine with distinct AID valies in the table. I was able to show the result for Amount and Create date and will leave it to you to continue since you will get the idea.
The critical part is to build a table with 10 rows times distinct AID so about 40 rows in table r. Then do a left join to table t which is similar to what you have done. Table t gets a rank of at most 10 records. Any missing rank up to 10 recs will be filled by table r. Coalesce will assign the default values such as 0 fro amount and 'no create date' for date.
http://sqlfiddle.com/#!9/855c21/2
SELECT coalesce(r.aid, t.aid) as aid,
coalesce(t.paymentvalue, 0) as paymentvalue,
coalesce(cast(t.created as char), 'no create date') as created
FROM (select * from (
select 1 as rw union
select 2 union select 3
union select 4 union select 5
union select 6 union select 7
union select 8 union select 9
union select 10) u
cross join (select distinct aid
from history) h
) as r
LEFT JOIN (
SELECT a.aid, a.paymentvalue,
a.created, count(*) rn
FROM history a
JOIN history b
ON a.aid = b.aid
AND a.created <= b.created
GROUP BY a.aid, a.created
HAVING COUNT(*) <= 10) t
on r.rw=t.rn and r.aid=t.aid
order by aid, created;
I have added RIGHT JOIN to bring in the null rows to top up to 10 (or n) rows per AID. Initially I use SELECT 1 UNION SELECT 2 ... to generate the 10 rows. In order to make it easier to increase the number of rows (say 100), I am trying this idea of generate_series equivalent for mysql. In order for this to work, the number of rows in history table must be equal to greater than the number of rows required per AID.
select t1.lid
,t2.aid
,coalesce(t1.created, "no created date") as created
,t1.typeID
,coalesce(t1.paymentdate, "no payment date") as paymentDate
,coalesce(t1.paymentvalue, 0) as paymentValue
,t2.rn
from
(
select *,
(#rn := if(#c = AID, #rn + 1,
if(#c := AID, 1, 1)
)
) as rn
from history cross join
(select #rn := 0, #c := -1) params
order by AID, Created desc
) t1
right join
( select *
from (select distinct aid from history ) h1
cross join
(select rn -- generate table with n rows numbered from 1 to n
from
(select
#num:= 0) init
cross join
(select #num := #num +1 rn
from history ) t -- assume history has at least 10 rows
limit
10 ) h2 -- n = 10; change it to the number of rows per aid required
) t2
on t1.aid = t2.aid and t1.rn = t2.rn
order by t2.aid, t2.rn
i have the following table :
+------+------+-----+---------+------------+
| no | code |eot |group_id | compulsary |
+------+------+-----+---------+------------+
| 1005 | 101 | 51 | 1 | 1 |
| 1005 | 102 | 67 | 1 | 1 |
| 1005 | 121 | 65 | 1 | 1 |
| 1005 | 231 | 82 | 2 | 0 |
| 1005 | 232 | 56 | 2 | 0 |
| 1005 | 233 | 45 | 2 | 1 |
| 1005 | 313 | 80 | 3 | 0 |
| 1005 | 443 | 50 | 4 | 0 |
|------+------+-----+---------+------------+
now what i want is :
1.) return all records where group_id=1,
2.) return the best two from group_id=2(however if compulsary=1 then include that row and the best from the remaining group_id=2),
3.)return 1 row each from group_id=3 and group_id=4 and if compulsary=1 then return that row
the final result should have only seven rows:
+------+------+-----+---------+------------+
| no | code |eot |group_id | compulsary |
+------+------+-----+---------+------------+
| 1005 | 101 | 51 | 1 | 1 |
| 1005 | 102 | 67 | 1 | 1 |
| 1005 | 121 | 65 | 1 | 1 |
| 1005 | 231 | 82 | 2 | 0 |
| 1005 | 233 | 45 | 2 | 1 |
| 1005 | 313 | 80 | 3 | 0 |
| 1005 | 443 | 50 | 4 | 0 |
|------+------+-----+---------+------------+
with the compulsary=1 rows inluded like above;
so far I have this query though I don't know how to check for the compulsary to get what I want:
select rg.*
from
(
select *
from
(
select
rgrade.*,
#rn := if(#gr=group_id,if(#gr:=group_id,#rn+1,#rn+1),if(#gr:=group_id,1,1)) as rn
from rgrade
cross join (select #rn:=0,#gr:=0) as vars
where admission_no=1005
) v
where (group_id=1)
or (group_id=2 and if(compulsary=1,rn<=1,rn<=2))
or (group_id in (3,4) and rn=1)
) rg
order by group_id;
the query returns the seven rows as expected but does not check for compulsary in group_id=2.
Any help much appreciated
You are describing several queries here, the result of which you want combined: All group_id = 1, the two best group_id = 2, the best of group_id = 3, and the best of group_id = 4. So write these queries and combine them with UNION ALL. With "best" defined as compulsary = 1 preferred, then highest eot, you get:
(select * from mytable where group_id = 1)
union all
(select * from mytable where group_id = 2 order by compulsary = 1 desc, eot desc limit 2)
union all
(select * from mytable where group_id = 3 order by compulsary = 1 desc, eot desc limit 1)
union all
(select * from mytable where group_id = 4 order by compulsary = 1 desc, eot desc limit 1)
order by group_id, no, code
;
You are trying to mimic standard SQL's
row_number() over (partition by group_id
order by case when compulsary = 1 then 1 else 0 end desc, eot desc)
with MySQL means. And I see the partitioning by group_id in your query, but I don't see any ORDER BY to get the best records first.
Here is my attempt on it. There may be mistakes; I'm no MySQL guy.
select
no, code, eot, group_id, compulsary
from
(
select
no,
code,
eot,
compulsary,
#row_number :=
case when group_id = #last_group_id then #row_number + 1 else 1 end as row_number,
#last_group_id := group_id as group_id
from rgrade
cross join
(
select
#row_number := 0,
#last_group_id := -1
) as vars
where admission_no = 1005
order by group_id, (compulsary = 1) desc, eot desc
) ranked
where (group_id = 1)
or (group_id = 2 and row_number <= 2)
or (group_id = 3 and row_number = 1)
or (group_id = 4 and row_number = 1)
order by group_id, code;
I have a table:
ID | User | Amount
1 | 1 | 50
2 | 1 | 80
3 | 2 | 80
4 | 2 | 100
5 | 1 | 90
6 | 1 | 120
7 | 2 | 120
8 | 1 | 150
9 | 2 | 300
I do a query:
SELECT * FROM TABLE ORDER BY amount DESC group by userid
I'm getting this:
ID | User | Amount
1 | 1 | 50
2 | 1 | 80
But I was expecting:
ID | User | Amount
9 | 2 | 300
8 | 1 | 150
What is wrong with my sql?
When grouping you have to use aggregate functions like max() for all columns that are not grouped by
select t.*
from table t
inner join
(
SELECT userid, max(amount) as total
FROM TABLE
group by userid
) x on x.userid = t.userid and x.total = t.amount
ORDER BY t.amount DESC
Another solution.Check SQL Fiddle
Using FIND_IN_SET clause
SELECT
ua.*
FROM user_amount ua
WHERE FIND_IN_SET(ua.amount,(SELECT
MAX(ua1.amount)
FROM user_amount ua1
WHERE ua1.user = ua.user)) > 0
ORDER BY amount desc;
Using IN clause
SELECT
ua.*
FROM user_amount ua
WHERE ua.amount IN (SELECT
MAX(ua1.amount)
FROM user_amount ua1
WHERE ua1.user = ua.user)
ORDER BY amount desc
I know it's not possible to use limits within nested INs, but I think there is a way to do this, I'm just not sure how.
I have a table that contains both ratings and comments (simplified for explanation)
mySingleTable:
+----+------------------+-----------+-----------+-----------------+
| id | reviewer_comment | is_rating | parent_id | reviewer_rating |
+----+------------------+-----------+-----------+-----------------+
| 1 | well done rateA | 1 | 0 | 5 Stars |
| 2 | commentAonRateA | 0 | 1 | |
| 3 | commentBonRateA | 0 | 1 | |
| 4 | commentConRateA | 0 | 1 | |
| 5 | commentDonRateA | 0 | 1 | |
| 6 | commentEonRateA | 0 | 1 | |
| 7 | commentFonRateA | 0 | 1 | |
| 8 | well done rateB | 1 | 0 | 4 Stars |
| 9 | well done rateC | 1 | 0 | 5 Stars |
| 11 | well done rateD | 1 | 0 | 3 Stars |
| 12 | well done rateE | 1 | 0 | 2 Stars |
| 13 | well done rateF | 1 | 0 | 5 Stars |
| 14 | well done rateG | 1 | 0 | 3 Stars |
| 15 | commentAonRateD | 0 | 11 | |
+----+------------------+-----------+-----------+-----------------+
So,
if is_rating = 1, its a rating.
if is_rating = 0, its a comment on the rating (its parent rating is where parent_id=id)
so this would look like:
well done rateA *****
commentAonRateA
commentBonRateA
commentConRateA
commentDonRateA
commentEonRateA
commentFonRateA
well done rateB ****
well done rateC *****
well done rateD ***
commentAonRateD
well done rateE **
well done rateF *****
well done rateG ***
What I want to do is select the newest five ratings, with the ASSOCIATED newest 5 comments, using only 1 query
So, some how join these two:
SELECT ratings.*
FROM mySingleTable as ratings
WHERE
is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5
SELECT comments.*
FROM mySingleTable as comments
Where
comments.parent_id = ratings.id
AND is_rating = 0
ORDER BY timestamp DESC LIMIT 0, 5
The 2nd query needs to somehow know about the ratings query
Please try this query which is now simplified and tested.
SELECT *
FROM
(SELECT *,
IF (group_id = #prev,
#n := #n + 1,
#n := 1 AND #prev := group_id) as position
FROM (
SELECT mySingleTable.*, group_id
FROM mySingleTable
INNER JOIN
(SELECT id AS group_id
FROM mySingleTable
WHERE is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5
) AS top5ratings
ON mySingleTable.id = group_id OR mySingleTable.parent_id = group_id
ORDER BY group_id DESC,
mySingleTable.parent_id ASC,
timestamp DESC) AS all_reviews
JOIN (SELECT #n := 0, #prev := 0) AS setup) AS reviews
WHERE reviews.position < 7
Keep in mind that SELECT * is bad practice. I used it to simplify reading.
TRY THIS QUERY
SELECT comments.*
FROM
(
SELECT ratings.id
FROM mySingleTable as ratings
WHERE
is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5 ) AS Top5
INNER JOIN mySingleTable AS comments ON comments.parent_id = Top5.id
WHERE comments.is_rating = 0
ORDER BY comments.timestamp DESC LIMIT 0, 5
Next weekend we're having a competition with 3 qualifications a semifinal and a final. Only the best 15 participants could compete in the semifinal. Only the best 6 compete in the Finals.
in the qualifications you get a score from 0 to 100 for each qualification
I'm looking to find a way to select the contesters for the semi-final. This should be based on (rank of qualification1) * (rank of qualification2) * (rank of qualification3)
so i need something like:
select id, name, ((.... as RANK_OF_SCORE_1) * (.. as RANK_OF_SCORE_2) * (... as RANK_OF_SCORE_3)) as qualification_score from participants order by qualification_score desc limit 15
but of course this is not valid mySQL.
Besides this problem if tho contesters have the same score, they should be both included in the semi-finals even if this exceeds the maximum of 15.
For the finals, we would like to select the best 6 of the semi-final scores. If 2 scores are the same we would like to select on the qualifications..
option 1 : use postgres, which support windowing functions (namely RANK() and DENSE_RANK())
SELECT user_id, score, rank() over (order by score desc) from scores;
Time : 0.0014 s
option 2 : use a self- join : the rank of a user with score X is (1 +the count(*) of users with score less than X) ; this is likely to be pretty slow
CREATE TABLE scores( user_id INT PRIMARY KEY, score INT, KEY(score) );
INSERT INTO scores SELECT id, rand()*100 FROM serie LIMIT 1000;
SELECT a.user_id, a.score, 1+count(b.user_id) AS rank
FROM scores a
LEFT JOIN scores b ON (b.score>a.score)
GROUP BY user_id ORDER BY rank;
+---------+-------+------+
| user_id | score | rank |
+---------+-------+------+
| 381 | 100 | 1 |
| 777 | 100 | 1 |
| 586 | 100 | 1 |
| 907 | 100 | 1 |
| 790 | 100 | 1 |
| 253 | 99 | 6 |
| 393 | 99 | 6 |
| 429 | 99 | 6 |
| 376 | 99 | 6 |
| 857 | 99 | 6 |
| 293 | 99 | 6 |
| 156 | 99 | 6 |
| 167 | 98 | 13 |
| 594 | 98 | 13 |
| 690 | 98 | 13 |
| 510 | 98 | 13 |
| 436 | 98 | 13 |
| 671 | 98 | 13 |
time 0.7s
option 3 :
SET #rownum = 0;
SELECT a.user_id, a.score, b.r FROM
scores a
JOIN (
SELECT score, min(r) AS r FROM (
SELECT user_id, score, #rownum:=#rownum+1 AS r
FROM scores ORDER BY score DESC
) foo GROUP BY score
) b USING (score)
ORDER BY r;
time : 0.0014 s
EDIT
SET #rownum1 = 0;
SET #rownum2 = 0;
SET #rownum3 = 0;
SELECT s.*, s1.r, s2.r, s3.r FROM
scores s
JOIN
(
SELECT score_1, min(r) AS r FROM (
SELECT score_1, #rownum1:=#rownum1+1 AS r
FROM scores ORDER BY score_1 DESC
) foo GROUP BY score_1
) s1 USING (score_1) JOIN (
SELECT score_2, min(r) AS r FROM (
SELECT score_2, #rownum2:=#rownum2+1 AS r
FROM scores ORDER BY score_2 DESC
) foo GROUP BY score_2
) s2 USING (score_2) JOIN (
SELECT score_3, min(r) AS r FROM (
SELECT score_3, #rownum3:=#rownum3+1 AS r
FROM scores ORDER BY score_3 DESC
) foo GROUP BY score_3
) s3 USING (score_3)
ORDER BY s1.r * s2.r * s3.r;