Mysql consecutive days returning wrong value - mysql

I am trying to do a consecutive days count in mysql from a timestamp column. I thought I had this problem solved once long ago - but for some strange reason, it is returning the wrong value.
It should return a single column containing a username, the lowest date with the highest time value in the consecutive date list, and the largest number of consecutive days.
It all works except the largest number of consecutive days is not always correct.
SELECT username, day1, count(distinct date) days from (
SELECT t1.username, t1.date, max(tmp.date) day1 from sites_surfed t1
LEFT JOIN (
SELECT tmp1.date from sites_surfed tmp1
LEFT JOIN sites_surfed tmp2
ON DATE_FORMAT(tmp1.date,'%d/%m/%Y') = DATE_FORMAT(tmp2.date,'%d/%m/%Y')+1
AND tmp1.username = tmp2.username where (tmp1.username = 'viraladmin')
AND (tmp2.date is null))tmp on (t1.date >= tmp.date) where t1.username = 'viraladmin'
GROUP BY t1.date
) fin
WEHERE username = 'viraladmin'
GROUP BY day1 having count(distinct date) > 1 order by days
The below jsfiddle shows an example of it returning 3 for the total amount of consecutive days when it should only be return 2 as only two unique dates are in the table list
http://sqlfiddle.com/#!9/85f6d6/4
What is going on here and how do I fix this?

I've changed
GROUP BY t1.date
to
GROUP BY DATE_FORMAT(t1.date,'%d/%m/%Y')
This seems to be working.
SELECT username, day1, count(distinct date) days from (
SELECT t1.username, t1.date, max(tmp.date) day1 from sites_surfed t1
LEFT JOIN (
SELECT tmp1.date from sites_surfed tmp1
LEFT JOIN sites_surfed tmp2
ON DATE_FORMAT(tmp1.date,'%d/%m/%Y') = DATE_FORMAT(tmp2.date,'%d/%m/%Y')+1
AND tmp1.username = tmp2.username where (tmp1.username = 'viraladmin')
AND (tmp2.date is null))tmp on (t1.date >= tmp.date) where t1.username = 'viraladmin'
GROUP BY DATE_FORMAT(t1.date,'%d/%m/%Y')
) fin
WHERE username = 'viraladmin'
GROUP BY day1 having count(distinct date) > 1 order by days

Related

Convert SumIfs Excel Function to MySQL

The formula in cell G2 "ReplenQty" is:
=SUMIFS(D:D,A:A,A2,B:B,B2,C:C,">=" & E2,C:C,"<=" &F2)
The formula in cell H2 "RpInVar" is:
=IF($A2<>$A1,ROUND(VAR(IF($A:$A=$A2,$G:$G)),2),0)
I attempted this in MySQL:
SELECT DISTINCT
Part,
Customer,
OrdDt,
OrdQty,
StartDate,
ReplenDate,
SUM(CASE WHEN Part = Part AND Customer = Customer AND OrdDt >= StartDate AND OrdDt <= ReplenDate THEN OrdQty ELSE 0 END) AS ReplenQty,
VARIANCE(CASE WHEN Part = Part AND Customer = Customer AND OrdDt >= StartDate AND OrdDt <= ReplenDate THEN OrdQty ELSE 0 END) AS RpInVar,
FROM
BeforeReplenQty
GROUP BY
Part,
Customer,
OrdDt,
OrdQty,
StartDate,
ReplenDate;
Problem is OrdQty and ReplenQty are the same and RpInVar are all 0.
This query is quite long and complicated but working on this demo: http://sqlfiddle.com/#!9/3b3334/70
One task is to do a sum where order date is between start date and replenish date.
Then get the row where part is new compared to previous row.
The first part of the query is to get the variance, the second subquery is to get the sum of Ordered qty and the sub-query at the bottom is to get the row where part column has changed.
select tab.Part,tab.Customer,tab.OrdDt,tab.OrdQty,tab.StartDate,tab.ReplenDate,tab.ReplenQty,
case when sumtab.Rnk=1 then
(select variance(ReplenQty)
from (select sum(t1.OrdQty) as ReplenQty
from BeforeReplenQty t2
inner join BeforeReplenQty t1
where t2.part=t1.part and t2.customer=t1.customer
and t2.OrdDt between t1.StartDate and t1.ReplenDate
group by t1.Part,t1.Customer,t1.OrdDt,t1.OrdQty,t1.StartDate,t1.ReplenDate) t3) else 0 end as ReplenVar
from (
select t1.*,sum(t1.OrdQty) as ReplenQty
from BeforeReplenQty t2
inner join BeforeReplenQty t1
where t2.part=t1.part and t2.customer=t1.customer
and t2.OrdDt between t1.StartDate and t1.ReplenDate
group by t1.Part,t1.Customer,t1.OrdDt,t1.OrdQty,t1.StartDate,t1.ReplenDate) tab
left join (select part,customer,orddt,rnk
from (
select t.part,t.customer,t.OrdDt,
#s:=CASE WHEN #c <> t.part THEN 1 ELSE #s+1 END AS rnk,
#c:=t.part AS partSet
from (SELECT #s:= 0) s
inner join (SELECT #c:= 'A') c
inner join (SELECT * from BeforeReplenQty
order by Part, Customer, OrdDt) t
) tab
where rnk = 1
) sumtab
on tab.part=sumtab.part and tab.customer=sumtab.customer and tab.orddt=sumtab.orddt;

MySql - Selecting MAX & MIN and returning the corresponding rows

I trying to get the last 6 months of the min and max of prices in my table and display them as a group by months. My query is not returning the corresponding rows values, such as the date time for when the max price was or min..
I want to select the min & max prices and the date time they both occurred and the rest of the data for that row...
(the reason why i have concat for report_term, as i need to print this with the dataset when displaying results. e.g. February 2018 -> ...., January 2018 -> ...)
SELECT metal_price_id, CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term, max(metal_price) as highest_gold_price, metal_price_datetime FROM metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
ORDER BY metal_price_datetime DESC
I have made an example, extract from my DB:
http://sqlfiddle.com/#!9/617bcb2/4/0
My desired result would be to see the min and max prices grouped by month, date of min, date of max.. and all in the last 6 months.
thanks
UPDATE.
The below code works, but it returns back rows from beyond the 180 days specified. I have just checked, and it is because it joining by the price which may be duplicated a number of times during the years.... see: http://sqlfiddle.com/#!9/5f501b/1
You could use twice inner join on the subselect for min and max
select a.metal_price_datetime
, t1.highest_gold_price
, t1.report_term
, t2.lowest_gold_price
,t2.metal_price_datetime
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, max(metal_price) as highest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t1 on t1.highest_gold_price = a.metal_price
inner join (
select a.metal_price_datetime
, t.lowest_gold_price
, t.report_term
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, min(metal_price) as lowest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t on t.lowest_gold_price = a.metal_price
) t2 on t2.report_term = t1.report_term
simplified version of what you should do so you can learn the working process.
You need calculate the min() max() of the periods you need. That is your first brick on this building.
you have tableA, you calculate min() lets call it R1
SELECT group_field, min() as min_value
FROM TableA
GROUP BY group_field
same for max() call it R2
SELECT group_field, max() as max_value
FROM TableA
GROUP BY group_field
Now you need to bring all the data from original fields so you join each result with your original table
We call those T1 and T2:
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R1
ON tableA.group_field = R1.group_field
AND tableA.value = R1.min_value
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R2
ON tableA.group_field = R2.group_field
AND tableA.value = R2.max_value
Now we join T1 and T2.
SELECT *
FROM ( .... ) as T1
JOIN ( .... ) as T2
ON t1.group_field = t2.group_field
So the idea is if you can do a brick, you do the next one. Then you also can add filters like last 6 months or something else you need.
In this case the group_field is the CONCAT() value

Advance Select Query - FIFO

I have a table with columns id, item, qty and expiration date. I have to select item with a given total qty based on expiration date. The item that expires first need to be the priority to select. The query works fine if the item has different expiration date but, my problem is if the item has the same expiration date it does not return any row. Please check out the query below.
SELECT t.ID,
t.itemcode,
t.qty,
t.expdate,
t.total,
t.prev_total,
CASE WHEN t.total > 500 THEN 500 - t.prev_total ELSE t.qty END AS total
FROM
(
SELECT t1.ID,
t1.itemcode,
t1.qty,
t1.expdate,
(SELECT SUM(t2.qty) FROM put_in t2
WHERE t2.expdate <= t1.expdate AND t2.itemcode = 'ITEM01') AS total,
COALESCE((SELECT SUM(t2.qty) FROM put_in t2
WHERE t2.expdate < t1.expdate AND t2.itemcode = 'ITEM01'), 0) AS prev_total
FROM put_in t1
WHERE t1.itemcode = 'ITEM01'
) t
WHERE t.total - t.qty < 500 AND
t.itemcode = 'ITEM01'
ORDER BY t.expdate;

Checking for maximum length of consecutive days which satisfy specific condition

I have a MySQL table with the structure:
beverages_log(id, users_id, beverages_id, timestamp)
I'm trying to compute the maximum streak of consecutive days during which a user (with id 1) logs a beverage (with id 1) at least 5 times each day. I'm pretty sure that this can be done using views as follows:
CREATE or REPLACE VIEW daycounts AS
SELECT count(*) AS n, DATE(timestamp) AS d FROM beverages_log
WHERE users_id = '1' AND beverages_id = 1 GROUP BY d;
CREATE or REPLACE VIEW t AS SELECT * FROM daycounts WHERE n >= 5;
SELECT MAX(streak) AS current FROM ( SELECT DATEDIFF(MIN(c.d), a.d)+1 AS streak
FROM t AS a LEFT JOIN t AS b ON a.d = ADDDATE(b.d,1)
LEFT JOIN t AS c ON a.d <= c.d
LEFT JOIN t AS d ON c.d = ADDDATE(d.d,-1)
WHERE b.d IS NULL AND c.d IS NOT NULL AND d.d IS NULL GROUP BY a.d) allstreaks;
However, repeatedly creating views for different users every time I run this check seems pretty inefficient. Is there a way in MySQL to perform this computation in a single query, without creating views or repeatedly calling the same subqueries a bunch of times?
This solution seems to perform quite well as long as there is a composite index on users_id and beverages_id -
SELECT *
FROM (
SELECT t.*, IF(#prev + INTERVAL 1 DAY = t.d, #c := #c + 1, #c := 1) AS streak, #prev := t.d
FROM (
SELECT DATE(timestamp) AS d, COUNT(*) AS n
FROM beverages_log
WHERE users_id = 1
AND beverages_id = 1
GROUP BY DATE(timestamp)
HAVING COUNT(*) >= 5
) AS t
INNER JOIN (SELECT #prev := NULL, #c := 1) AS vars
) AS t
ORDER BY streak DESC LIMIT 1;
Why not include user_id in they daycounts view and group by user_id and date.
Also include user_id in view t.
Then when you are queering against t add the user_id to the where clause.
Then you don't have to recreate your views for every single user you just need to remember to include in your where clause.
That's a little tricky. I'd start with a view to summarize events by day:
CREATE VIEW BView AS
SELECT UserID, BevID, CAST(EventDateTime AS DATE) AS EventDate, COUNT(*) AS NumEvents
FROM beverages_log
GROUP BY UserID, BevID, CAST(EventDateTime AS DATE)
I'd then use a Dates table (just a table with one row per day; very handy to have) to examine all possible date ranges and throw out any with a gap. This will probably be slow as hell, but it's a start:
SELECT
UserID, BevID, MAX(StreakLength) AS StreakLength
FROM
(
SELECT
B1.UserID, B1.BevID, B1.EventDate AS StreakStart, DATEDIFF(DD, StartDate.Date, EndDate.Date) AS StreakLength
FROM
BView AS B1
INNER JOIN Dates AS StartDate ON B1.EventDate = StartDate.Date
INNER JOIN Dates AS EndDate ON EndDate.Date > StartDate.Date
WHERE
B1.NumEvents >= 5
-- Exclude this potential streak if there's a day with no activity
AND NOT EXISTS (SELECT * FROM Dates AS MissedDay WHERE MissedDay.Date > StartDate.Date AND MissedDay.Date <= EndDate.Date AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND MissedDay.Date = B2.EventDate))
-- Exclude this potential streak if there's a day with less than five events
AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND B2.EventDate > StartDate.Date AND B2.EventDate <= EndDate.Date AND B2.NumEvents < 5)
) AS X
GROUP BY
UserID, BevID

mysql find date where no row exists for previous day

I need to select how many days since there is a break in my data. It's easier to show:
Table format:
id (autoincrement), user_id (int), start (datetime), end (datetime)
Example data (times left out as only need days):
1, 5, 2011-12-18, 2011-12-18
2, 5, 2011-12-17, 2011-12-17
3, 5, 2011-12-16, 2011-12-16
4, 5, 2011-12-13, 2011-12-13
As you can see there would be a break between 2011-12-13 and 2011-12-16. Now, I need to be able say:
Using the date 2011-12-18, how many days are there until a break:
2011-12-18: Lowest sequential date = 2011-12-16: Total consecutive days: 3
Probably: DATE_DIFF(2011-12-18, 2011-12-16)
So my problem is, how can I select that 2011-12-16 is the lowest sequential date? Remembering that data applies for particular user_id's.
It's kinda like the example here: http://www.artfulsoftware.com/infotree/queries.php#72 but in the reverse.
I'd like this done in SQL only, no php code
Thanks
SELECT qmin.start, qmax.end, DATE_DIFF( qmax.end, qmin.start ) FROM table AS qmin
LEFT JOIN (
SELECT end FROM table AS t1
LEFT JOIN table AS t2 ON
t2.start > t1.end AND
t2.start < DATE_ADD( t1.end, 1 DAY )
WHERE t1.end >= '2011-12-18' AND t2.start IS NULL
ORDER BY end ASC LIMIT 1
) AS qmax
LEFT JOIN table AS t2 ON
t2.end < qmin.start AND
t2.end > DATE_DIFF( qmin.start, 1 DAY )
WHERE qmin.start <= '2011-12-18' AND t2.start IS NULL
ORDER BY end DESC LIMIT 1
This should work - left joins selects one date which can be in sequence, so max can be fineded out if you take the nearest record without sequential record ( t2.anyfield is null ) , same thing we do with minimal date.
If you can calculate days between in script - do it using unions ( eg 1. row - minimal, 2. row maximal )
Check this,
SELECT DATEDIFF((SELECT MAX(`start`) FROM testtbl WHERE `user_id`=1),
(select a.`start` from testtbl as a
left outer join testtbl as b on a.user_id = b.user_id
AND a.`start` = b.`start` + INTERVAL 1 DAY
where a.user_id=1 AND b.`start` is null
ORDER BY a.`start` desc LIMIT 1))
DATEDIFF() show difference of the Two days, if you want to number of consecutive days add one for that result.
If it's not a beauty contents then you may try something like:
select t.start, t2.start, datediff(t2.start, t.start) + 1 as consecutive_days
from tab t
join tab t2 on t2.start = (select min(start) from (
select c1.*, case when c2.id is null then 1 else 0 end as gap
from tab c1
left join tab c2 on c1.start = adddate(c2.start, -1)
) t4 where t4.start <= t.start and t4.start >= (select max(start) from (
select c1.*, case when c2.id is null then 1 else 0 end as gap
from tab c1
left join tab c2 on c1.start = adddate(c2.start, -1)
) t3 where t3.start <= t.start and t3.gap = 1))
where t.start = '2011-12-18'
Result should be:
start start consecutive_days
2011-12-18 2011-12-16 3