Getting the latest n records for each group - mysql

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

Related

Mysql group by (custom number for result)

I have a table
Id Name Id_collection Price
1 good1 2 10
2 good2 1 101
3 good3 3 102
4 good4 2 10
5 good5 2 10
I need to Group By id_collection, but i need to show 2 rows (to be able to change this value via variable or ... Ex: to change to 3 or 4 )
not
1
2
3
Ex
1
1
2
2
3
3
or
Ex
1
1
1
2
2
2
3
3
3
so the result must be
Id Name Id_collection Price
1 good1 2 10
4 good4 2 10
2 good2 1 101
3 good3 3 102
I was thinking about procedure or loop, but i didn't that before, Please help!!!
You want to group by adjacent values . . . in MySQL. You can use variables to assign the group. Alternatively, you can use this method to assign the group: count the number of rows that have id_collection different from each row with a smaller id.
You don't specify how to calculate the other columns, but here is a guess:
select min(id) as id, min(name) as name, id_collection, avg(price) as price
from (select t.*,
(select count(*)
from t t2
where t2.id_collection <> t.id_collection and
t2.id < t.id
) as grp
from t
) t
group by id_collection, grp;
EDIT:
I just realized that you probably don't want to aggregate the results; you probably just want the first row. For that, use variables:
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn,
if(#id := id_collection, #rn + 1, #rn + 1)
)
) as rn
from t cross join
(select #id = -1, #rn := 0) params
order by id
) t
where rn = 1;
I edited a little the answer which #Gordon_Linoff posted, and it works now, you just have to change rn <= 3 this number and will get the various results.
#Gordon_Linoff - Thank you, this really helped me
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn + 1,
if(#id := id_collection, 1, 0)
)
) as rn
from t cross join
(select #id := -1, #rn := 0) params
order by id_collection
) t
where rn <= 3;

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 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.

Numbering duplicates record in mysql (2)

I have this Table , (sequence_No.) Field is null :
ID Name age sequence_No.
-- ----- --- ------------
1 sara 20
2 sara 20
3 sara 20
4 john 24
5 john 24
6 Hama 23
I want to Update it to this:
ID Name age sequence_No.
-- ----- --- ------------
1 sara 20 1
2 sara 20 2
3 sara 20 3
4 john 24 1
5 john 24 2
6 Hama 23 1
Which query can do that in mysql?
thank you
You can emulate ROW_NUMBER() using correlated subquery in mysql. The resulting table with sequential number will be join with the table itself and update the value of sequence_No using the generated numbers.
UPDATE tableName a
INNER JOIN
(
SELECT A.ID,
(
SELECT COUNT(*)
FROM tableName c
WHERE c.Name = a.Name AND
c.ID <= a.ID) AS sequence_No
FROM TableName a
) b ON a.ID = b.ID
SET a.sequence_No = b.sequence_No
SQLFiddle Demo
SELECT ID, Name, age, sequence_No
FROM
(
select ID,
Name,
age,
#sum := if(#nme = Name AND #acct = age, #sum ,0) + 1 sequence_No,
#nme := Name,
#acct := age
from TableName,
(select #nme := '', #sum := 0, #acct := '') vars
order by Name, age
) s
ORDER BY ID
Or you may use
SELECT
ID,
Name,
age,
(
CASE Name
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := Name END
) + 1 AS sequence_No
FROM student, (SELECT #curRow := 0, #curType := '') r
ORDER BY ID,NAME;
this would work though i have not tested yet you can use the same to update your table

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/