How to populate number for missing dates? - mysql

I have a table that look like this
date counts
Sep-01 1
Sep-05 5
Sep-10 30
As you can see the dates are jumping across and is not continuous. I want the final results to be populated for all calendar dates, and the value for the missing dates should be equal to the previous date. I played around with several SQL analytics functions but it doesn't seem to solve the problem. Any tips?
date counts
Sep-01 1
Sep-02 1
Sep-03 1
Sep-04 1
Sep-05 5
Sep-06 5
Sep-07 5
Sep-08 5
Sep-09 5
Sep-10 30
Sep-11 30
...

Here is solution:
CREATE TABLE datadate(startdate DATETIME, cnt INT);
INSERT INTO datadate VALUES('2020-05-25',1);
INSERT INTO datadate VALUES('2020-05-27',2);
INSERT INTO datadate VALUES('2020-05-30',5);
SELECT * FROM datadate;
WITH RECURSIVE cte_name (cnt,ddate) AS (
SELECT cnt,MIN(startdate) AS ddate FROM datadate
UNION ALL
SELECT
CASE
WHEN (SELECT cnt FROM datadate WHERE startdate=DATE_ADD(ddate, INTERVAL 1 DAY)) IS NULL THEN cnt
ELSE (SELECT cnt FROM datadate WHERE startdate=DATE_ADD(ddate, INTERVAL 1 DAY))
END AS cnt,
DATE_ADD(ddate, INTERVAL 1 DAY)
FROM cte_name
WHERE ddate < (SELECT MAX(startdate) FROM datadate)
)
SELECT * FROM cte_name;
Input:
Output:

Related

Count Number of a Specific Day(s) Between Two Dates

I have a single line in MySQL table: volunteers
user_id | start_date | end_date
11122 | 2017-04-20 | 2018-02-17
How can I find how many times the 3rd day or 24th day of a month appears? (i.e. 2017-05-03, 2017-06-03, 2017-12-24, 2018-01-24) I'm trying to get to the following count:
Sample Output:
user_id | number_of_third_day | number_of_twenty_fourth_day
11122 | 10 | 10
I look at the documentation online to see if there is a way I can say (pseudo):
SELECT
day, COUNT(*)
FROM volunteers
WHERE day(between(start_date, end_date)) in (3,24)
I tried to create a calendar table to no avail, but I would try to get the days, GROUP BY day, and COUNT(*) times that day appears in the range
WITH calendar AS (
SELECT start_date AS date
FROM volunteers
UNION ALL
SELECT DATE_ADD(start_date, INTERVAL 1 DAY) as date
FROM volunteers
WHERE DATE_ADD(start_date, INTERVAL 1 DAY) <= end_date
)
SELECT date FROM calendar;
Thanks for any help!
This one is more optimized since I generate date range by months not days as other questions, so its faster
WITH RECURSIVE cte AS
(
SELECT user_id, DATE_FORMAT(start_date, '%Y-%m-03') as third_day,
DATE_FORMAT(start_date, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM table1
UNION ALL
SELECT user_id,
DATE_FORMAT(third_day + INTERVAL 1 MONTH, '%Y-%m-03') as third_day,
DATE_FORMAT(twenty_fourth_day + INTERVAL 1 MONTH, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM cte
WHERE third_day + INTERVAL 1 MONTH <= end_date
)
SELECT user_id,
SUM(CASE WHEN third_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_third_day,
SUM(CASE WHEN twenty_fourth_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_twenty_fourth_day
FROM cte
GROUP BY user_id;
Demo here
A dynamic approach is.
but creating the dateranges, takes a lot of time, so you should have a date table to get the dates
CREATE TABLE table1
(`user_id` int, `start_date` varchar(10), `end_date` varchar(10))
;
INSERT INTO table1
(`user_id`, `start_date`, `end_date`)
VALUES
(11122, '2017-04-20', '2018-02-17')
,(11123, '2019-04-20', '2020-02-17')
;
Records: 2 Duplicates: 0 Warnings: 0
WITH RECURSIVE cte AS (
SELECT
user_id,
`start_date` as date_run ,
`end_date`
FROM table1
UNION ALL
SELECT
user_id,
DATE_ADD(cte.date_run, INTERVAL 1 DAY),
end_date
FROM cte
WHERE DATE_ADD(date_run, INTERVAL 1 DAY) <= end_date
)SELECT user_id,
SUM(DAYOFMONTH(date_run) = 3) as day_3th,
SUM(DAYOFMONTH(date_run) = 24) as day_24th
FROM cte
GROUP BY user_id
user_id
day_3th
day_24th
11122
10
10
11123
10
10
fiddle
In last MySQL version you can use recursion:
-- get list of all dates in interval
with recursive dates(d) as (
select '2017-04-20'
union all
select date_add(d, interval 1 day) from dates where d < '2018-02-17'
) select
-- calculate
sum(day(d) = 10) days_10,
sum(day(d) = 24) days_24
from dates
-- filter 10 & 24 days
where day(d) = 10 or day(d) = 24;
https://sqlize.online/sql/mysql80/c00eb7de69d011a85502fa538d64d22c/
As long as you are looking for days that occur in every month (so not the 29th or beyond), this is just straightforward math. The number of whole calendar months between two dates (exclusive) is:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
Then add one if the start month includes the target day and one if the end month includes it:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
+ (day(start_date) <= 3) + (day(end_date) >= 3)

Show number of lower and higher values in SQL

I have a certain problem while trying to make an SQL. I have a table with the following format and data.
id
value
date
12
3
2020-06-01
12
4
2020-06-09
12
1
2020-06-20
5
4
2020-06-11
5
5
2020-06-17
My goal is to make something like that:
id
lower
higher
12
1
1
5
0
1
This looks for the value of the oldest row IN specific interval (ex. 100 days)and it compares it with all dates after that if their values are higher and lower and return the count.
I do have something that works but it requires more queries:
One to group take all ids with dates in the interval of xx days
SELECT id FROM table
WHERE date >= CURDATE() - INTERVAL 30 DAY GROUP BY id
ORDER BY id ASC;
And then I loop through each row and get its lower and higher values.
SELECT
*
FROM
(
SELECT
COUNT(*) AS higher, id
FROM
`table`
WHERE
id = 12 AND date > CURDATE() - INTERVAL 30 DAY AND value > (
SELECT value FROM table
WHERE table.date >= CURDATE() - INTERVAL 30 DAY AND id = 12
ORDER BY `table`.`date` ASC LIMIT 1
)
) AS t1,(
SELECT
COUNT(*) AS deteriorated_placements
FROM
`table`
WHERE
id = 12 AND date > CURDATE() - INTERVAL 30 DAY AND value < (
SELECT value FROM table
WHERE table.date >= CURDATE() - INTERVAL 30 DAY AND id = 12
ORDER BY `table`.`date` ASC LIMIT 1
)
) AS t2;
The problem with that is that I do around 40 more queries. I know it maybe is not a big issue but
Is there a way to somehow combine those 2 queries?
Use first_value():
select id,
sum(value < value_1) as lower,
sum(value > value_1) as higher
from (select t.*,
first_value(value) over (partition by id order by date) as value_1
from t
) t
group by id;

A query for getting results separated by a date gap

ID
TIMESTAMP
1
2020-01-01 12:00:00
2
2020-02-01 12:00:00
3
2020-05-01 12:00:00
4
2020-06-01 12:00:00
5
2020-07-01 12:00:00
I am looking for a way to get records in a MySQL database that are within a certain range of each other. In the above example, notice that there is a month between the first two records, then a three month gap, before we see another three records with a month between.
What is a way to group these into two result sets, so I will get Ids 1, 2 and 3, 4, 5 A solution using days would be probably work the best as thats easier to modify.
You can use lag() and then logic to see where a gap is big enough to start a new set of records. A cumulative sum gives you the groups you want:
select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t;
If you want to summarize this with a start and end date:
select min(timestamp), max(timestamp)
from (select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t
) t
group by grouping;
For example, the following query:
select group_concat(ID)
from (
select w1.ID,w1.TS,w2.ID flag
from work1 w1 left outer join work1 w2
on timestampdiff(month,w2.TS,w1.TS)=1
order by w1.ID
) w
group by
case when flag is null then #str:=ID else #str end
See db fiddle

Getting zero value from empty days

I need to return the count of item sold in last 30 days and
return 0 if there is no item sold. My current query is working well for
getting days where there is item sold but it is not returning 0 values and
I need it.
Here is my current query
SELECT DATE_FORMAT(FROM_UNIXTIME(purchase_date), '%m/%d') AS Dates, COUNT(*) AS COUNT
FROM purchases
WHERE FROM_UNIXTIME(purchase_date) BETWEEN NOW() - INTERVAL 30 DAY AND NOW()
GROUP BY Dates
ORDER BY Dates ASC
The actual result are:
02/15 -> 10
02/16 -> 12
02/18 -> 22
But I'm missing the empty date(s) that I want to return as 0.
This is the table structure:
userid int(11), purchase_item int(11), purchase_date int(11)
You can generate the last 30 days using a recursive CTE:
with recursive dates as (
select curdate() as dte, 1 as n
union all
select dte - interval 1 day, n + 1
from dates
where n < 30
)
The rest is just incorporating this into a query using a LEFT JOIN or correlate subquery:
with recursive dates as (
select curdate() as dte, 1 as n
union all
select dte - interval 1 day, n + 1
from dates
where n < 30
)
select d.dte, count(p.purchase_date)
from dates d left join
purchases p
on from_unixtime(purchase_date) >= d.dte and
from_unixtime(purchase_date) < d.dte + interval 1 day
group by d.dte;

SQL query to extract the most recent part and anything that is 30 days older than that date.

I am very new to SQL and I need to write a query that selects data for a specific part. However, It should select only the part that is the most recent(given by date) and anything that is only 30 days prior to it. Please consider the table below:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
2 BBB 6/15/2015
3 AAA 6/11/2015
4 AAA 1/1/2008
I need a query that gives me:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
3 AAA 6/11/2015
I have tried:
select * from ( select * from sales_table where Part_NAME = 'AAA') where DATE BETWEEN (max(DATE) and (max(DATE)-30))
I have read some articles saying that I cannot use WHERE and functions like max() together and advised me to use group by or having but it didn't work for me as well. Thank you.
IF you want data from the last 30 days of the current day, you can do :
SELECT *
FROM sales_table
WHERE
[DATE] >= DATEADD(DAY, -30,GETDATE())
AND [DATE] <= GETDATE()
AND Part_NAME = 'AAA'
IF you want data from the last 30 days from the last date of sale of each Part_NAME (this will take the max recorded date of sale for each Part_NAME and get the last 30 days records of each one of them.)
SELECT *
FROM (
SELECT *,
MAX([DATE]) OVER(PARTITION BY Part_NAME ORDER BY PartID) AS RecentDate
FROM sales_table
) D
WHERE
[DATE] >= DATEADD(DAY, -30, RecentDate)
AND [DATE] <= RecentDate
AND Part_NAME = 'AAA'
You can accomplish by using datediff and getdate() and a subquery.
SELECT * FROM (
SELECT *,DATEDIFF(DD,[DATE],GETDATE()) AS DAYSBETWEEN FROM sales_table
) AS X
WHERE DAYSBETWEEN <= 30
If you want data from the last 30 days, it would be:
select st.*
from sales_table st join
(select top (1) st2.*
from sales_table st2
order by st2.date desc
) st2
on st2.part_name = st.part_name and
st.date >= dateadd(day, -30, cast(getdate() as date));