How to group continuous ranges using MySQL - 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/

Related

Mysql - Create view combined 2 tables but only merged by select without merge tables

In MySQL, I have 2 tables, named table_rebate and table_bonus
table_rebate ( have 3 same columns, 2 different columns )
a_id a_value a_time
1 1000 2018-05-05 10:25:15
2 3000 2018-05-05 11:35:15
table_bonus ( have 3 same columns, 3 different colums )
b_id b_value b_time
01 500 2018-05-05 11:20:15
02 700 2018-05-05 12:30:15
I need to select that 3 same columns into 1 tables to my PHP (CI) views.
Number from Values Time
1 Rebate 1000 2018-05-05 10:25:15
2 Bonus 500 2018-05-05 11:20:15
3 Bonus 700 2018-05-05 11:35:15
4 Rebate 3000 2018-05-05 12:30:15
How can I do this ? It's not to be merged, but need to print like merged table and can be sort by (a_time & b_time) ascending.
EXPLAIN select (#rn := #rn + 1) as id, `from`, `values`, `time`
from ((select 'rebate' as `from`, a_value as `values`, a_time as `time`
from table_rebate
) union all
(select 'bonus' as `from`, b_value, b_time
from table_bonus
)
) br cross join
(select #rn := 0) params
order by `time`;
You can use union all:
select (#rn := #rn + 1) as id, `from`, `values`, `time`
from ((select 'rebate' as `from`, a_value as `values`, a_time as `time`
from table_rebate
) union all
(select 'bonus' as `from`, b_value, b_time
from table_bonus
)
) br cross join
(select #rn := 0) params
order by `time`;
Note that from, values, and time are all keywords in SQL (even if not reserved). That makes them very bad names for columns.

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

find second highest from each group in sql?

i have table structure like this
'encounterID' , 'dateAndTime'
1234 2016-02-12 17:57:57
1234 2016-02-12 17:58:59
1234 2016-02-12 17:59:05
12345 2016-02-12 17:57:57
12345 2016-02-12 17:58:59
12345 2016-02-12 17:59:05
i want to find second latest entry for every encounterId?
Anyone Please help
You can use variables to select top-n-per-group records:
SELECT encounterID, dateAndTime
FROM (
SELECT encounterID, dateAndTime,
#rn := IF(#eID = encounterID, #rn + 1,
IF(#eID := encounterID, 1, 1)) AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0, #eID := 0) AS vars
ORDER BY encounterID, dateAndTime DESC) AS t
WHERE t.rn = 2
The outer query selects the second latest record per encounterID group.
Demo here

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