SELECT user ranking on MySQL by an offset - mysql

I'm trying to make a query in MySQL that returns me only 10 users from the table, but with a rank value that is the ORDER result of the xp column. Right now I have this:
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM usuarios, (SELECT #curRank := 0) r
ORDER BY xp DESC LIMIT 10;
It looks to be working perfectly when fetching the first 10 users.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 373901344995803138 | 5863 | 1 |
| 701198768049225770 | 5692 | 2 |
| 239203656405221376 | 4961 | 3 |
| 692489002942726154 | 4508 | 4 |
| 416988898628206593 | 3669 | 5 |
| 312003290378534912 | 3155 | 6 |
| 608344569381126167 | 3059 | 7 |
| 671949142473310238 | 3041 | 8 |
| 549743978191519744 | 2991 | 9 |
| 592440479577145383 | 2519 | 10 |
+--------------------+------+------+
But when I try to fetch for example LIMIT 10,10 to get the users between 11 and 20, although they are ordered, their global rank is incorrect because #curRank is not increasing for all the users before the offset.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 638196238436532234 | 1888 | 1 |
| 601269358349516833 | 1447 | 2 |
| 548357514497097743 | 1338 | 3 |
| 203591312031744000 | 1330 | 4 |
| 379034072519016469 | 1283 | 5 |
| 563804445654122497 | 1086 | 6 |
| 421296425981181952 | 1025 | 7 |
| 263816867100098560 | 850 | 8 |
| 631330775379214371 | 776 | 9 |
| 442529076511637504 | 702 | 10 |
+--------------------+------+------+
I don't know a way to make the global ranking work when using LIMIT.

In MySQL 8.0, just use window functions, as demonstrated by Gordon Linoff.
In earlier versions, you basically need a subquery to do what you want. I would recommend:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
ORDER BY xp DESC
LIMIT 10, 10;
The subquery ranks all users first, then you can safely filter in the outer query. Note that the query pre-orders the table by xp in a subquery first: this is safer (user variables are tricky in MySQL).
Actually, you don't even needLIMIT in the outer query; you can use a WHERE clause instead:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
WHERE rank BETWEEN 11 AND 20
ORDER BY rank

Instead, use row_number():
SELECT id, xp, row_number() over (order by cp desc) as rnk
FROM usuarios
ORDER BY xp DESC
LIMIT 10;

Related

MySQL - Select Top 5 with Rankings

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

How to attribute dynamic values for table in mysql

I have 3 tables, 1 with prices, another with customers and the last with dependents of customers like a health insurance. When I have 1 customer and 1 dependent the value for the first dependent is one, when I have two dependents the value of second dependent is different, but the first is the same.
I need a query that shows the dependents table and gets the value of each dependent, 4 dependents are the maximum.
Dependent Table
+--------------+--------+---------+------------+
| id_dependent | name | number | primary_id |
+--------------+--------+---------+------------+
| 51 | Carlos | 956585 | 2 |
| 52 | João | 985868 | 2 |
| 53 | Jaime | 985868 | 2 |
| 54 | Evan | 985847 | 3 |
| 55 | Kaus | 584788 | 3 |
+--------------+--------+---------+------------+
Price Table
+----------+---------+-----------+-------+---------+
| price_id | Product | Dependent | Value | Plan_id |
+----------+---------+-----------+-------+---------+
| 11 | Plan1 | 1 | 15,00 | 56 |
| 12 | Plan1 | 2 | 13,50 | 56 |
| 13 | Plan1 | 3 | 11,50 | 56 |
+----------+---------+-----------+-------+---------+
What I need
+--------------+--------+--------+------------+-------+
| id_dependent | name | number | primary_id | Value |
+--------------+--------+--------+------------+-------+
| 51 | Carlos | 956585 | 2 | 15,00 |
| 52 | João | 985868 | 2 | 13,50 |
| 53 | Jaime | 985868 | 2 | 11,50 |
| 54 | Evan | 985847 | 3 | 15,00 |
| 55 | Kaus | 584788 | 3 | 13,50 |
+--------------+--------+--------+------------+-------+
How can I do this?
You can use row_number() to enumerate the dependents and then join:
select d.*, p.price
from (select d.*, row_number() over (partition by primary_id order by id_dependent) as seqnum
from dependents d
) d left join
price p
on p.dependent = d.seqnum and p.plan_id = 56;
In earlier versions of MySQL, you can use variables:
select d.*, p.price
from (select d.*,
(#rn := if(#p = d.primary_id, #rn + 1,
if(#p := d.primary_id, 1, 1)
)
) as seqnum
from (select d.* from dependents d order by primary_id, id_dependent) d cross join
(select #p := -1, #rn := 0) params
) d left join
price p
on p.dependent = d.seqnum and p.plan_id = 56;
Notes on the use of variables:
They are deprecated and may be removed in future versions of MySQL.
The order by is in a subquery; that is needed in some versions of MySQL. Variables and order by don't always play well together.
Both variables are assigned in the same expression. MySQL does not guarantee the order of evaluation of expressions, so this is very important for working code.
This works with mysql 5.6 and 5.7
Select d.`id_dependent`, d.`name`, d.`number`, d.`primary_id`, p.`Value`
From (
SELECT d.*,
if (#primid = primary_id,#curRank := #curRank + 1,#curRank := 1) AS rank,
#primid := primary_id
FROM Dependent d, (SELECT #curRank := 0) r , (SELECT #primid := 0) s
ORDER BY primary_id,id_dependent) d
left join price p on p.Dependent = d.rank
Order by d.`id_dependent`;
Which results in
id_dependent name number primary_id Value
51 Carlos 956585 2 15,00
52 João 985868 2 13,50
53 Jaime 985868 2 11,50
54 Evan 985847 3 15,00
55 Kaus 584788 3 13,50

Leaderboard with rank - MySQL

I have a table like this
+-----+-------+
| pid | score |
+-----+-------+
| 1 | 120|
| 2 | 130|
| 3 | 100|
| 1 | 120|
| 2 | 130|
| 3 | 100|
+-----+-------+
I am trying to get get my users ranking. Currently I am using this query,
SELECT pid
, SUM(score) AS total_score
FROM score
GROUP
BY pid
ORDER
BY total_score DESC
I am getting a sorted list by score with this query but not getting their position. I want something like this
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 2 | 260| 1 |
| 1 | 240| 2 |
| 3 | 200| 3 |
+-----+-------+------+
And can I get this position for a specific user? Like
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 1 | 240| 2 |
+-----+-------+------+
I am using MySQL 5.7.21. Can you please help?
If you have the chance to be in Mysql 8.0.2 or more you have the function rank.
Else You can use a variable to set the rank:
SELECT pid,
total_score,
#curRank := #curRank + 1 AS rank
FROM
(SELECT pid,
sum(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC) datas, (SELECT #curRank := 0) r
You can filter after to get only the result for the pid you want
Yes, you can use LIMIT like:
SELECT pid, SUM(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC
LIMIT 1

Group, count and concat laps

I can't resolve this problem (race), I need show the time for each LAP. This is times table
id | race_id | car_num | time |
+-----+-------------+---------+------------+
1 | 8 | 25 | 00:09:05 |
2 | 8 | 33 | 00:09:35 |
3 | 8 | 10 | 00:09:55 |
4 | 8 | 25 | 00:18:15 |
5 | 8 | 33 | 00:19:05 |
6 | 8 | 25 | 00:39:45 |
I tried this query:
SELECT
car_num, COUNT(car_num) as laps, race_id, concat(vlap,'-',time) as times
FROM
(SELECT num_car, concat(time,'-',v1) vlap
FROM tiempos) vti
GROUP BY
car_num
This is output required:
car_num | laps | race_id | times |
+-----+-------------+---------+------------------------------------------------+
25 | 3 | 8 | lap1 00:09:05, lap2 00:18:15, lap3 00:39:45 |
33 | 2 | 8 | lap1 00:09:35, lap2 00:19:05 |
10 | 1 | 8 | lap1 00:09:55 |
I'm dizzy, some idea please
You can get most of what you want with a simple group_concat():
select car_num, count(*) as laps, race_id, group_concat(time order by id separator ', ' ) as times
from tiempos t
group by car_num, race_id;
If you need the lap number, you can get that using variables:
select car_num, count(*) as laps, race_id, group_concat('lap', rn, ' ', time order by id separator ', ' ) as times
from (select t.*,
(#rn := if(#t = time, #rn + 1,
if(#t := time, 1, 1)
) as rn
from tiempos t cross join
(select #rn := 0, #t := '') vars
order by race_id, car_num, time
) t
group by car_num, race_id;

Limiting the output of specific column sql hana

I have a table structure as given below and what I'd like to be able to do is retrieve the top three records with the highest value for each Company code.
I've googled and I couldn't find a better way so hopefully you guys can help me out.
By the way, I'm attempting this in MySQL and SAP HANA. But I am hoping that I can grab the "structure" if the query for HANA if I can get help for only MySQL
Thanks much!
Here's the table:
http://pastebin.com/xgzCgpKL
In MySQL you can do
To get exactly three records per group (company) no matter ties emulating ROW_NUMBER() analytic function. Records with the same value get the same rank.
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, #n + 1, 1) rnum, #g := company
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
Get all records per group that have a rank from 1 to 3 emulating DENSE_RANK() analytic function
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, IF(#v = value, #n, #n + 1), 1) rnum, #g := company, #v := value
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL, #v := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 1 | E | 3 |
| 1 | G | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
| 3 | G | 3 |
Here is SQLFiddle demo
UPDATE: Now it looks like HANA supports analytic functions so the queries will look like
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
ROW_NUMBER() OVER (PARTITION BY company ORDER BY value DESC) rnum
FROM table1
)
WHERE rnum <= 3;
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
DENSE_RANK() OVER (PARTITION BY company ORDER BY value DESC) rank
FROM table1
)
WHERE rank <= 3;
Here is SQLFiddle demo It's for Oracle but I believe it will work for HANA too