SQL Query to generate a leaderboard and selecting one players information - mysql

Assume we have the following MYSQL-Query to generate a leaderboard:
SELECT x.player_id, x.position,x.leaderboard_value
FROM (SELECT player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
which produces a leaderboard like this:
Textfile as CSV: https://www.dropbox.com/s/70xw3ocbonqs98s/sql.csv?dl=0
How do I change or extend the query to deliver a specific players position, and the total amount of positions (highest position)
for above table and player_id=10649 I'd expect a result-table with 1 row containing these fields:
position: 6,
totalpositions: 20,
percentage= 0.3 (which is 6/20)
Backstory of this is to join this percentage with a different table rank_map(rank_id,minvalue,maxvalue) defining ranks (rank is "9" when this value is between 0.2 and 0.4 for example)
In the end this query should simply return
rank: 9
as answer
Thank you very much.
UPDATE:
with Gordon Linoffs answer, using this query:
SELECT MAX(CASE WHEN player_id = 10649 THEN position END) as play_position,
COUNT(*) as total_position,
MAX(CASE WHEN player_id = 10649 THEN position END) / COUNT(*) as ratio
FROM (SELECT t1.*, #rownum := #rownum + 1 AS position
FROM table1 t1 CROSS JOIN
(SELECT #rownum := 0) r
WHERE restrictive_value < 200
ORDER BY leaderboard_value DESC
) x;
I can get this table:
A final step is left, which is, how to join it with the rank(rank_id, minvalue,maxvalue) table to only get the rank_id row where ratio is between minvalue and maxvalue?

You can achieve this by adding COUNT(*) to your subquery JOIN to get the total number of rows, then display the percentage as position/total:
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
Some of the output from your sample data:
player_id position leaderboard_value percentage
2730 1 1090 0.01
1369848 2 1017 0.02
1665922 3 960 0.03
1607632 4 910 0.04
1853500 5 909 0.05
10649 6 883 0.06
1538490 7 877 0.07
1898051 8 866 0.08
1510162 9 828 0.09
1898129 10 825 0.1
1863538 11 821 0.11
1522562 12 806 0.12
1380267 13 805 0.13
1404318 14 797 0.14
8793 15 769 0.15
21793 16 767 0.16
14658 17 756 0.17
1690659 18 729 0.18
1429094 19 723 0.19
1727977 20 719 0.2
SQLFiddle Demo
To get the data for only a specific player, just add a WHERE x.player_id=nnnn clause e.g.
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
WHERE x.player_id = 10649
Output:
player_id position leaderboard_value percentage
10649 6 883 0.06
To then get their ranking from the rank table, you just need to JOIN it based on percentage (note you have to use the formula as you can't use an alias in a JOIN):
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage, m.rank_id
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
JOIN rank_map m ON m.minvalue <= (x.position/x.total) AND m.maxvalue > (x.position/x.total)
WHERE x.player_id = 10649
Output:
player_id position leaderboard_value percentage rank_id
10649 6 883 0.06 3
Updated Demo

For this purpose, you can use conditional aggregation:
SELECT MAX(CASE WHEN player_id = 10649 THEN position END) as play_position,
COUNT(*) as total_position,
MAX(CASE WHEN player_id = 10649 THEN position END) / COUNT(*) as ratio
FROM (SELECT t1.*, #rownum := #rownum + 1 AS position
FROM table1 t1 CROSS JOIN
(SELECT #rownum := 0) r
WHERE restrictive_value < 200
ORDER BY leaderboard_value DESC
) x;
You can use either COUNT(*) or MAX(position) for the highest position.

Related

running average in mysql

I have the table like below
id timestamp speed
1 11:00:01 100
2 11:05:01 110
3 11:10:01 90
4 11:15 :01 80
I need to calculate moving average like below
id timestamp speed average
1 11:00:01 100 100
2 11:05:01 110 105
3 11:10:01 90 100
4 11:15:01 80 95
What I tried
SELECT
*,
(select avg(speed) from tbl t where tbl.timestamp<=t.timestamp) as avg
FROM
tbl
At first it looks quite easy but when the data on the table swell, it is too slow
Any faster approach?
Your query is one way to do a running average:
SELECT t.*,
(select avg(speed) from tbl tt where tt.timestamp <= t.timestamp) as avg
FROM tbl t;
The alternative is to use variables:
select t.*, (sum_speed / cnt) as running_avg_speed
from (select t.*, (#rn := #rn + 1) as cnt, (#s := #s + speed) as sum_speed
from tbl t cross join
(select #rn := 0, #s := 0) params
order by timestamp
) t;
An index on tbl(timestamp) should further improve performance.
Does MySQL support windowing functions?
select
id, timestamp, speed,
avg (speed) over (order by timestamp) as average
from tbl
If it doesn't this might work, although I doubt it's efficient:
select
min (t1.id) as id, t1.timestamp, min (t1.speed) as speed,
avg (t2.speed)
from
tbl t1
join tbl t2 on
t2.id <= t1.id
group by
t1.timestamp
order by
t1.timestamp
Or slotting neatly between GL's two answers (performancewise anyway)...
SELECT x.*, AVG(y.speed) avg
FROM my_table x
JOIN my_table y
ON y.id <= x.id
GROUP
BY x.id;
What about a simple concurrent solution?
SET #summ=0; SET #counter=0;SELECT *,(#counter := #counter +1) as cnt, (#summ := #summ+speed) as spd, (#summ/#counter) AS avg FROM tbl;

MySQL Return multiple rows if aggregated value is more than 1

Table has aggregated values but i need to return multiple rows if the value is greater than one.
Here is how the table looks now:
date description amount
1/1/2015 alpha 3
1/1/2015 beta 1
Here is how i need it to return:
date description amount
1/1/2015 alpha 1
1/1/2015 alpha 1
1/1/2015 alpha 1
1/1/2015 beta 1
Any help would be greatly appreciated.
You need a table of numbers. Something like this works for up to 3 and can be easily extended:
select t.date, t.description, 1 as amount
from table t join
(select 1 as n union all select 2 union all select 3) n
on n.n <= t.amount;
EDIT:
If you have enough rows in the table for the larger amounts, you can do:
select t.date, t.description, 1 as amount
from table t join
(select #rn := #rn + 1 as n
from table cross join (select #rn := 0) vars
) n
on n.n <= t.amount;
This worked perfectly.
select t.date, t.description, 1 as amount
from table t join
(select #rn := #rn + 1 as n
from table cross join (select #rn := 0) vars
) n
on n.n <= t.amount;

MySQL to calculate ranking and update the original table

MySQL server 5.6.20 (latest version at the moment)
Given a price by date table. I added a new column "Rank", which represent the ranking to the item price by date.
Date Item Price Rank
1/1/2014 A 5.01 0
1/1/2014 B 31 0
1/1/2014 C 1.5 0
1/2/2014 A 5.11 0
1/2/2014 B 20 0
1/2/2014 C 5.5 0
1/3/2014 A 30 0
1/3/2014 B 11.01 0
1/3/2014 C 22 0
How do I write a SQL statement to calculate the ranking and update the original table? Below is the expected table with ranking filled in. The ranking calculation is grouped by date (1/1, 1/2, 1/3, etc).
Date Item Price Rank
1/1/2014 A 5.01 2
1/1/2014 B 31 1
1/1/2014 C 1.5 3
1/2/2014 A 5.11 3
1/2/2014 B 20 1
1/2/2014 C 5.5 2
1/3/2014 A 30 1
1/3/2014 B 11.01 3
1/3/2014 C 22 2
Also, if the price is the same for several items, how would MySQL handle the ranking? For example:
Date Item Price Rank
1/4/2014 A 31 0
1/4/2014 B 31 0
1/4/2014 C 1.5 0
Thanks.
You can get the rank in a query using varibles:
select t.*,
(#rn := if(#d = date, #rn + 1,
if(#d := date, 1, 1)
)
) as rank
from pricebydate t cross join
(select #d := NULL, #rn := 0) vars
order by date, price desc;
You can put this in an update using a join:
update pricebydate pbd join
(select t.*,
(#rn := if(#d = date, #rn + 1,
if(#d := date, 1, 1)
)
) as rank
from pricebydate t cross join
(select #d := NULL, #rn := 0) vars
order by date, price desc
) r
on pbd.date = r.date and pbd.item = item
set pbd.rank = r.rank;
I believe this will do exactly what you want:
Update YourTable As T1
Set ItemRank = (
Select ItemRank From (
Select Rank() Over (Partition By ItemDate Order By Price Desc)
As ItemRank, Item, ItemDate
From YourTable
) As T2
Where T2.Item = T1.Item
And T2.ItemDate = T1.ItemDate
)
Duplicate Ranks would be handled as having equal ranks.

mySQL Ranks Ties To Next Higher Number

Rank with Ties works correctly except that the first record is always missing, no matter how it's fetched. Trying to rank low to high.
SELECT x.*,
COUNT(*) AS myRank
FROM myTablename x
JOIN myTablename y
ON x.number > y.number
GROUP BY id
ORDER BY myRank
PLEASED TO SAY, AFTER A LONG BATTLE, THE FOLLOWING REVISION WORKS AS INTENDED!
SELECT x.*,
Count(y.id)+1 AS myRank
FROM myTablename x
LEFT JOIN myTablename y
ON (x.number > y.number)
GROUP BY x.id
ORDER BY myRank
Result:
ID # Rank
A -50 1
B -40 2
C -40 2
D -30 4
E -30 4
Adding "=" to... ON x.number >= y.number ...gets ranking with ties right, except that the tied records rank to the next higher number.
ID # Rank
A -50 1
B -40 3
C -40 3
D -30 5
E -30 5
Ranking High-to-low with ON x.number <= y.number does the same thing.
Here's a version without a self-join but rather using variables. It's most likely also faster:
select id, points, rank from (
select
t.*,
#rownum := #rownum + 1,
#rank := if(#prev_points = points, #rank, #rownum) as rank,
#prev_points := points
from
test t,
(select #rank:=0, #rownum:=0, #prev_points) var_init
order by points desc
)sq
see it working live in an sqlfiddle

Getting the sum of all rows

I have a query that is something like the one below. My goal is to add all the 'total_points' and retrieve the single result of the sum of all the rows.
SELECT
SUM(
COALESCE(
CASE
WHEN COUNT(DISTINCT `table_1`.views) > 50 OR COUNT(DISTINCT `table_1`.views) = 50 AND COUNT(DISTINCT `table_1`.views) < 100 THEN COUNT(DISTINCT `table_1`.views) +5
ELSE COUNT(DISTINCT `table_1`.views)
END,0)
+
CASE
WHEN COUNT(DISTINCT `table_2`.views) > 50 OR COUNT(DISTINCT `table_2`.views) = 50 AND COUNT(DISTINCT `table_2`.views) < 100 THEN COUNT(DISTINCT `table_2`.views) +5
ELSE COUNT(DISTINCT `table_2`.views)
END,0)) AS sum
FROM `table_1`
LEFT JOIN `table_2`
ON `table_1`.id = `table_2`.id
GROUP BY `table_1`.primary_id
This will give me a result of something like this
rank | total_points
1 321
2 111
3 100
4 90
5 72
6 60
7 45
8 23
9 11
10 5
This is my desire results:
sum |
838
Try this:
SELECT IFNULL(rank, 'Sum') rank, total_points
FROM (SELECT #rank := #rank + 1 AS rank, T1.total_points
FROM (SELECT COALESCE((CASE WHEN COUNT(DISTINCT t1.views) betwnn 50 AND 100 THEN COUNT(DISTINCT t1.views) + 5 ELSE COUNT(DISTINCT t1.views) END) +
(CASE WHEN COUNT(DISTINCT t2.views) betwnn 50 AND 100 THEN COUNT(DISTINCT t2.views) + 5 ELSE COUNT(DISTINCT t2.views) END), 0) AS total_points
FROM table_1 t1
LEFT JOIN table_2 t2 ON t1.id = t2.id
GROUP BY t1.primary_id
ORDER BY total_points
) AS T1, (SELECT #rank := 0) AS r
ORDER BY total_points DESC
) AS A
GROUP BY rank WITH ROLLUP