Find max of continuous streak and the current streak from datetime - mysql

I have the following data of a particular user -
Table temp -
time_stamp
2015-07-19 10:52:00
2015-07-18 10:49:00
2015-07-12 10:43:00
2015-06-08 12:32:00
2015-06-07 11:33:00
2015-06-06 10:05:00
2015-06-05 04:17:00
2015-04-14 04:11:00
2014-04-02 23:19:00
So the output for the query should be -
Maximum streak = 4, Current streak = 2
Max streak = 4 because of these -
2015-06-08 12:32:00
2015-06-07 11:33:00
2015-06-06 10:05:00
2015-06-05 04:17:00
And current streak is 2 because of these (Assuming today's date is 2015-07-19)-
2015-07-19 10:52:00
2015-07-18 10:49:00
EDIT: I want a simple SQL query for MYSQL

For MAX streak(streak) you can use this, I have use the same query to calculate max streak. This may help you
SELECT *
FROM (
SELECT t.*, IF(#prev + INTERVAL 1 DAY = t.d, #c := #c + 1, #c := 1) AS streak, #prev := t.d
FROM (
SELECT date AS d, COUNT(*) AS n
FROM table_name
group by date
) AS t
INNER JOIN (SELECT #prev := NULL, #c := 1) AS vars
) AS t
ORDER BY streak DESC LIMIT 1;

A general approach with the gaps and islands queries is to tag each row with its rank in the data and with its rank in the full list of dates. The clusters will all have the same difference.
Caveats: I don't know if this query will be efficient. I don't remember if MySQL allows for scalar subqueries. I didn't look up the way to calculate a day interval in MySQL.
select user_id, max(time_stamp), count(*)
from (
select
t.user_id, t.time_stamp,
(
select count(*)
from T as t2
where t2.user_id = t.user_id and t2.time_stamp <= t.time_stamp
) as rnk,
number of days from t.time_stamp to current_date as days
from T as t
) as data
group by usr_id, days - rnk

Related

Calculating total of days between multiple date ranges

I have a problem figuring out how to calculate total days between different date ranges using MySQL.
I need to count total of days between different date ranges without days that include each other date range.
Data example:
from
to
2021/08/28
2021/09/29
2021/08/29
2021/09/01
2021/09/01
2021/09/01
Date ranges example and output
Dates 2021-08-28 2021-08-29 2021-08-30 2021-08-31 2021-09-01 2021-09-02 2021-09-03 2021-09-04
Range1 |--------------------|
Range2 |--------------------|
Range3 |--------------------|
Total Days: 6
Dates 2021-08-28 2021-08-29 2021-08-30 2021-08-31 2021-09-01 2021-09-02 2021-09-03 2021-09-04
Range1 |--------------------|
Range2 |--------------------------------------------|
Range3 |--------|
Total Days: 5
Possibly the simplest method is a recursive CTE:
with recursive dates as (
select `from`, `to`
from t
union all
select `from` + interval 1 day, `to`
from dates
where `from` < `to`
)
select count(distinct `from`)
from dates;
Note that from and to are really bad names for columns because they are SQL keywords.
EDIT:
In MySQL 5.7, you can use a tally table -- a table of numbers.
Assuming your original table has enough rows for the widest time span, you can use:
select count(distinct `from` + interval (n - 1) day)
from t cross join
(select (#rn := #rn + 1) as n
from t cross join
(select #rn := 0) params
) n
on `from` + interval (n - 1) day <= `to`;
If your table is really big, you might want a limit for the widest time period.

How to calculate reverse running sum in MySQL

I have this query where I calculated cumulative sum. Now, I need to calculate reverse cumulative sum for the same variable
SELECT t1.date, t1.ant, t1.hab,
(#csum:= #csum + t1.hab) as cumulative_hab
from(
SELECT date,
ant,
sum(num_habit) as hab
from xxxxxxxxxx
WHERE date BETWEEN CURDATE() - INTERVAL 5 DAY AND CURDATE()
group by ant) AS t1
,(select #csum := 0) vars
order by t1.ant
My table look like this
date ant hab cumulative_hab
24-05-2020 0 382,000 382,000
24-05-2020 1 28,000 410,000
24-05-2020 2 26,000 436,000
24-05-2020 3 11,000 447,000
24-05-2020 4 29,000 476,000
24-05-2020 6 6,000 482,000
24-05-2020 7 12,000 494,000
28-05-2020 8 50,000 544,000
24-05-2020 12 5,000 549,000
24-05-2020 13 6,000 555,000
I would like another column with reverse running sum (reverse cumulative sum), the first value is calculated 555 - 382
date ant hab cumulative_hab reverse_cum_hab
24-05-2020 0 382,000 382,000 555,000
24-05-2020 1 28,000 410,000 173,000,
24-05-2020 2 26,000 436,000 145,000
24-05-2020 3 11,000 447,000 119,000
24-05-2020 4 29,000 476,000 108,000
24-05-2020 6 6,000 482,000 79,000
24-05-2020 7 12,000 494,000 73,000
28-05-2020 8 50,000 544,000 61,000
24-05-2020 12 5,000 549,000 11,000
24-05-2020 13 6,000 555,000 6,000
As a starter: if you are running MySQL 8.0, you can do this easily with window functions:
select
date,
ant,
sum(num_habit) as hab,
sum(sum(num_habit)) over(order by date) cumulative_hab,
sum(sum(num_habit)) over(order by date desc) reverse_cumulative_hab
from mytable
where date between current_date - interval 5 day and current_date
group by date, ant
order by date
In earlier versions, it is more complicated. I would suggest joining two queries:
select t.*, r.reverse_cumulative_hab
from (
select t.*, #csum := #csum + hab cumulative_hab
from (
select date, ant, sum(num_habit) as hab
from mytable
where date between current_date - interval 5 day and current_date
group by date, ant
order by date
) t
cross join (select #csum := 0) x
) t
inner join (
select t.*, #rcsum := #rcsum + hab reverse_cumulative_hab
from (
select date, ant, sum(num_habit) as hab
from mytable
where date between current_date - interval 5 day and current_date
group by date, ant
order by date desc
) t
cross join (select #rcsum := 0) x
) r on r.date = t.date
order by t.date
This assumes no duplicate ant per date.
It might also be possible to simplify the logic and compute the reverse sum by taking the difference between the cumulative sum and the overall sum:
select t.*, z.total_hab - t.cumulative_hab reverse_cumulative_hab
from (
select t.*, #csum := #csum + hab cumulative_hab
from (
select date, ant, sum(num_habit) as hab
from mytable
where date between current_date - interval 5 day and current_date
group by date, ant
order by date
) t
cross join (select #csum := 0) x
) t
cross join (
select sum(num_habit) as total_hab
from mytable
where date between current_date - interval 5 day and current_date
) z
order by date
Note that these queries are safer than your original code in regard of ordering of the rows: records are ordered in a subquery before the cumulative sum is computed.

MySql select relative similar dates

I trying to select records from a table that are in the same time window, for example
record_date | record_data
4/20/2015 5:00:00 PM | 23
4/20/2015 5:08:00 PM | 3
4/20/2015 5:09:00 PM | 98
if i set 2 minutes window will be result in:
4/20/2015 5:08:00 PM | 3
4/20/2015 5:09:00 PM | 98
but, if i choose 15 minutes window must get:
4/20/2015 5:00:00 PM | 23
4/20/2015 5:08:00 PM | 3
4/20/2015 5:09:00 PM | 98
how can be done that ? BETWEEN or DATEDIFF statements needs a absolute date to compare with, and in this case the comparison must be relative to other records values and not to an external time.
Mysql isn't very good at this (which means - there's nothing built in for it).
What we need to do, is associate a rank with each row, based on record_date order. ie, first row gets a rank of 1, the immediate next row by date gets a rank of 2, and so forth.
If we do that, we can then compare one row to the next fairly easily (ie compare rank to rank + 1)
This will give us pairings of rows, and the immediate next row by date, which we can then filter for those that are separated by your window of choice.
In order to transform that result, into a two column result, we need to repeat the query, union the results together, taking the first date and data in the first query, and the second date and data in the second query.
union is by default distinct, so we dont get any repeated rows. This gives us the following query:
select * from (
select t1.record_date, t1.record_data from
(select #rank := #rank + 1 as rank, records.*
from records, (select #rank := 0) q order by record_date asc) t1
left join
(select #rank2 := #rank2 + 1 as rank, records.*
from records, (select #rank2 := 0) q order by record_date asc) t2
on t1.rank + 1 = t2.rank
where t2.record_date < t1.record_date + interval 2 minute
union
select t2.record_date, t2.record_data from
(select #rank := #rank + 1 as rank, records.*
from records, (select #rank := 0) q order by record_date asc) t1
left join
(select #rank2 := #rank2 + 1 as rank, records.*
from records, (select #rank2 := 0) q order by record_date asc) t2
on t1.rank + 1 = t2.rank
where t2.record_date < t1.record_date + interval 2 minute
) q
order by record_date asc;
with a demo here
Or alternatively, you can do it in a slightly less over-the-top manner like this:
select *
from (
select r1.record_date, r1.record_data
from records r1
inner join records r2
on r2.record_date = (select min(record_date) from records where record_date > r1.record_date)
where r2.record_date < r1.record_date + interval 2 minute
union
select r2.record_date, r2.record_data
from records r1
inner join records r2
on r2.record_date = (select min(record_date) from records where record_date > r1.record_date)
where r2.record_date < r1.record_date + interval 2 minute
) q
order by record_date asc;
Where we dont both with ranking, and just join directly to the next sequential date via a subquery that finds it.
Updated demo here

MySQL SELECT output next date per id in same row

I have the following table structure that I query on.
ID Date Before value After value
1 2014-04-25 Win Loss
1 2014-04-30 Loss Win
1 2014-08-18 Win Loss
1 2014-08-27 Loss Remise
1 2014-09-05 Remise Loss
2 2014-05-25 Win Remise
2 2014-06-07 Remise Win
2 2014-06-20 Win Loss
As output of my select statement I want the following result:
ID Start_Date End_Date After value
1 start 2014-04-25 Win
1 2014-04-26 2014-04-30 Loss
1 2014-05-01 2014-08-18 Win
1 2014-08-19 2014-08-27 Loss
1 2014-08-28 2014-09-05 Remise
1 2014-09-06 current Loss
2 start 2014-05-25 Win
2 2014-05-26 2014-06-07 Remise
2 2014-06-08 2014-06-20 Win
2 2014-06-21 current Loss
Of course the table has 1000's of records where it needs to go right. I tried with ranking and joining, but no luck yet. If there is a next row I somehow need to retrieve this value as start date +1.
http://sqlfiddle.com/#!2/520b13/2
This approach uses MySql inline variables to preserve the last ID and last date for comparison to the next record.
SELECT
t.id,
IF( #LastID = t.id, #LastDate, 'start ' ) as StartDate,
IF( NOT t.date = t2.date, t.date, 'current ' ) as EndDate,
t.after_value,
#LastID := t.id as saveForNextLineID,
#LastDate := t.date + INTERVAL 1 day as saveForNextDate
from
the_table t
left join ( select id, max( date ) as date
from the_table
group by id ) t2
ON t.id = t2.id,
(select #LastID := 0, #LastDate := 'start ' ) sqlvars
order by
t.id,
t.date
SQL Fiddle sample
Reading between the lines somewhat but you might be able to use UNION to manually add a start and end date eg:
select * from mytable
union
select DISTINCT ID,'2000-01-01' as `Date`,`Before value`,``After value` from mytable
union
select DISTINCT ID,NOW() as `Date`,`Before value`,``After value` from mytable
Try Popping this in a sub-select and run your grouping code on that.
Something like:
select t1.id,
(select max(date) + interval '1' day
from the_table t2
where t2.date < t1.date
and t2.id = t1.id) as start_date,
t1.date as end_date,
t1.after_value
from the_table t1
order by t1.id, t1.date;
SQLFiddle: http://sqlfiddle.com/#!2/8905a/3

calculating the rank for today's and yesterday total using mysql query

I have table as below
name price date
soap 10 2013-09-18
soap 10 2013-09-18
pens 8 2013-09-18
deos 7 2013-09-18
book 13 2013-09-17
book 13 2013-09-17
soap 10 2013-09-17
pens 8 2013-09-17
Based on the above data , i would like to calculate the rank of total selling per item ( based on price as below for today and previous day.
name totalselling date todayrank previousdayrank
soap 20 2013-09-18 1 2
pens 8 2013-09-18 2 2
deos 7 2013-09-18 3 -
if the todays item doesnt exit in yesterday ,then its ranking in previous day should be null.
You will have to use subselects, date intervals, some variables, a case and some brainwork.
Mix all this together and you will get something like this:
SELECT today.*,
CASE
WHEN yesterday.yesterdayrank IS NULL THEN '-'
ELSE yesterday.yesterdayrank
END
FROM (SELECT #i:=#i +1 AS todayrank,
name,
SUM(price) AS price
FROM test,
(SELECT #i:= 0) AS foo
WHERE createdate = DATE (NOW()) - INTERVAL 2 DAY
GROUP BY name
ORDER BY todayrank) today
LEFT JOIN (SELECT #j:= #j +1 AS yesterdayrank,
name,
SUM(price) AS price
FROM test,
(SELECT #j:= 0) AS bar
WHERE createdate = DATE (NOW()) - INTERVAL 3 DAY
GROUP BY name
ORDER BY yesterdayrank) yesterday ON today.name = yesterday.name
I hope this helps.
SELECT a.name,
a.todaysales,
a.date,
a.rank AS ranktoday,
b.rank AS rankyesterday
FROM
(SELECT name, sum(price) AS todaysales, date, #n := #n + 1 AS rank
FROM TABLE , (SELECT #n := 0) alias
WHERE date=curdate() group by 1 order by count(*))a
INNER JOIN
(SELECT name, sum(price) AS todaysales, date, #n := #n + 1 AS rank
FROM TABLE , (SELECT #n := 0) alias
WHERE date=curdate() - interval 1 DAY group by 1 order by count(*))b ON a.name=b.name