MySQL to calculate ranking and update the original table - mysql

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.

Related

SQL Query to generate a leaderboard and selecting one players information

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.

Getting the latest n records for each group

Lets say I have the following table:
id coulmn_id value date
1 10 'a' 2016-04-01
1 11 'b' 2015-10-02
1 12 'a' 2016-07-03
1 13 'a' 2015-11-11
2 11 'c' 2016-01-10
2 23 'd' 2016-01-11
3 11 'c' 2016-01-09
3 111 'd' 2016-01-11
3 222 'c' 2016-01-10
3 333 'd' 2016-01-11
for n = 3, I want to get the latest n records<=3 for each id. So I will have the following output:
id column_id value date
1 10 'a' 2016-04-01
1 12 'a' 2016-07-03
1 13 'a' 2015-11-11
2 11 'c' 2016-01-10
2 23 'd' 2016-01-11
3 111 'd' 2016-01-11
3 222 'c' 2016-01-10
3 333 'd' 2016-01-11
I am answering because the referenced question has an unstable answer (I'll comment on that there).
Here is a solution that should work:
select t.*
from (select t.*,
(#rn := if(#id = id, #rn + 1,
if(#id := id, 1, 1)
)
) as seqnum
from t cross join
(select #rn := 0, #id := -1) params
order by id, date desc
) t
where seqnum <= 3;
The difference in the solutions is that the variable assignments are all in a single expression. MySQL does not guarantee the order of evaluation of expressions, so this is very important if the code is going to work consistently.
You could do this with the use of variables. First go through the results in reverse order and assign a row number, then filter the results for row numbers less or equal to 3, and re-order:
select id, value, date
from (
select id, value, date,
#rn := if(#id = id, #rn+1, if (#id := id, 1, 1)) rn
from mytable,
cross join (#id := null, #rn := null) init
order by id, date desc
) as base
where rn <= 3
order by id, date asc

How to get temporal sequence by mysql

In my table there is an id column, a date column and a status column like this:
ID DATE STATUS
1 0106 A
1 0107 A
1 0112 A
1 0130 B
1 0201 A
2 0102 C
2 0107 C
I want to get a temporal sequence of each ID. Which means if in the neighboring time one id is in the same status, then the former ones will be omitted. The query result is like:
ID DATE STATUS
1 0112 A
1 0130 B
1 0201 A
2 0107 C
How can I realize it by MySQL?
You have to use variable to do this:
select `id`, `date`, `status`
from (
select *, #rowno:=if(#grp = `STATUS`, #rowno + 1 , 1) as rowno, #grp := `STATUS`
from yourtable
cross join (select #grp := null, #rowno := 0) t
order by `id`, `date` desc
) t1
where rowno = 1
order by `id`, `date`
SqlFiddle Demo

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;

How to group continuous ranges using MySQL

I have a table that contains categories, dates and rates. Each category can have different rates for different dates, one category can have only one rate at a given date.
Id CatId Date Rate
------ ------ ------------ ---------
000001 12 2009-07-07 1
000002 12 2009-07-08 1
000003 12 2009-07-09 1
000004 12 2009-07-10 2
000005 12 2009-07-15 1
000006 12 2009-07-16 1
000007 13 2009-07-08 1
000008 13 2009-07-09 1
000009 14 2009-07-07 2
000010 14 2009-07-08 1
000010 14 2009-07-10 1
Unique index (catid, Date, Rate)
I would like for each category to group all continuous dates ranges and keep only the begin and the end of the range.
For the previous example, we would have:
CatId Begin End Rate
------ ------------ ------------ ---------
12 2009-07-07 2009-07-09 1
12 2009-07-10 2009-07-10 2
12 2009-07-15 2009-07-16 1
13 2009-07-08 2009-07-09 1
14 2009-07-07 2009-07-07 2
14 2009-07-08 2009-07-08 1
14 2009-07-10 2009-07-10 1
I found a similar solution in the forum which did not exactly give the result
WITH q AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CatId, Rate ORDER BY [Date]) AS rnd,
ROW_NUMBER() OVER (PARTITION BY CatId ORDER BY [Date]) AS rn
FROM my_table
)
SELECT CatId AS catidd, MIN([Date]) as beginn, MAX([Date])as endd, Rate
FROM q
GROUP BY CatId, rnd - rn, Rate
SEE SQL FIDDLE
How can I do the same thing in mysql?
Please help!
MySQL doesn't support analytic functions, but you can emulate such behaviour with user-defined variables:
SELECT CatID, Begin, MAX(Date) AS End, Rate
FROM (
SELECT my_table.*,
#f:=CONVERT(
IF(#c<=>CatId AND #r<=>Rate AND DATEDIFF(Date, #d)=1, #f, Date), DATE
) AS Begin,
#c:=CatId, #d:=Date, #r:=Rate
FROM my_table JOIN (SELECT #c:=NULL) AS init
ORDER BY CatId, Rate, Date
) AS t
GROUP BY CatID, Begin, Rate
See it on sqlfiddle.
SELECT catid,min(ddate),max(ddate),rate
FROM (
SELECT
Catid,
Ddate,
rate,
#rn := CASE WHEN (#prev <> rate
or DATEDIFF(ddate, #prev_date)>1) THEN #rn+1 ELSE #rn END AS rn,
#prev := rate,
#prev_id := catid ,
#prev_date :=ddate
FROM (
SELECT CatID,Ddate,rate
FROM rankdate
ORDER BY CatID, Ddate ) AS a ,
(SELECT #prev := -1, #rn := 0, #prev_id:=0 ,#prev_date:=-1) AS vars
) T1 group by catid,rn
Note: The line (SELECT #prev := -1, #rn := 0, #prev_id:=0 ,#prev_date:=-1) AS vars is not necessary in Mysql Workspace, but it is in the PHP mysql_query function.
SQL FIDDLE HERE
I know I am late, still posting a solution that worked for me.
Had the same issue, here's how I got it
Found a good solution using variables
SELECT MIN(id) AS id, MIN(date) AS date, MIN(state) AS state, COUNT(*) cnt
FROM (
SELECT #r := #r + (#state != state OR #state IS NULL) AS gn,
#state := state AS sn,
s.id, s.date, s.state
FROM (
SELECT #r := 0,
#state := NULL
) vars,
t_range s
ORDER BY
date, state
) q
GROUP BY gn
More details at : https://explainextended.com/2009/07/24/mysql-grouping-continuous-ranges/