a WITH workaround for MySQL - mysql

How to convert this with part to MySQL?
SET #start = '20210101';
SET #end = '20211231';
WITH cte AS
(
SELECT dt = DATEADD(DAY, -(DAY(#start) - 1), #start)
UNION ALL
SELECT DATEADD(MONTH, 1, dt)
FROM cte
WHERE dt < DATEADD(DAY, -(DAY(#end) - 1), #end)
)
SELECT DATENAME(MONTH,dt) Name, DAY(EOMONTH(dt)) as Days into RESULT_TABLE
FROM cte
P.S. as far as I know I cant use WITH in MySQL since version 8.0. But what about version prior to 8.0?

You can create a table from a recursive query this way:
SET #start = '20210101';
SET #end = '20211231';
CREATE TABLE result_table AS
WITH RECURSIVE cte(dt) AS
(
SELECT DATE(#start)
UNION ALL
SELECT DATE_ADD(dt, INTERVAL 1 MONTH)
FROM cte
WHERE DATE_ADD(dt, INTERVAL 1 MONTH) < #end
)
SELECT MONTHNAME(dt) AS Name, DAY(LAST_DAY(dt)) AS Days
FROM cte;

In mysql 8 or mariadb 10.2+, you would do this like:
WITH RECURSIVE cte AS (
SELECT DATE_SUB(#start, INTERVAL DAY(#start) - 1 DAY) AS dt
UNION ALL
SELECT DATE_ADD(dt, INTERVAL 1 MONTH)
FROM cte
WHERE dt < DATE_SUB(#end, INTERVAL DAY(#end) - 1 DAY)
)
SELECT MONTHNAME(dt) AS Name, DAY(LAST_DAY(dt)) AS Days
FROM cte;
But I'm not sure I understand what the "into RESULT_TABLE" was doing.

Related

cte table does not exist mysql

I am trying to calculate the number of users who have an active subscription on a particular day. I also want information related to the subscription plan they are on. I have a subscriptions table which includes the start date and end date of subscription as well as the plan name. I am using a recursive cte to find out the number of subscribers of different plans on a date range but I am getting the error that the cte table doesn't exist. I am using the following code.
SET #start = (SELECT MIN(start_date) FROM subscriptions);
SET #end = (SELECT MAX(end_date) FROM subscriptions);
WITH cte AS (
SELECT #start dt
UNION ALL
SELECT date_add(dt, interval 1 day) FROM cte
WHERE dt < #end
)
SELECT cte.dt, SUM(CASE WHEN subscriptions.plan_name IS NULL THEN 0 ELSE 1 END) FROM cte
LEFT JOIN subscriptions t
ON cte.dt BETWEEN t.start_date AND t.end_date
GROUP BY cte.dt;
the output should look like this
WITH cte AS (
SELECT #start dt
UNION ALL
SELECT date_add(dt, interval 1 day) FROM cte
WHERE dt < #end
)
You are refering cte in itself, which makes it recursive and needs to be defined as such (WITH RECURSIVE).
This is what you need:
WITH RECURSIVE cte AS (
SELECT #start dt
UNION ALL
SELECT date_add(dt, interval 1 day) FROM cte
WHERE dt < #end
)

How to get the last 30 days results with MySQL with 0 value too?

I'm trying to get all my result of last 30 days, getting 0 when the count is 0.
My query is:
SELECT substring(mc.publication_date, 1, 10) AS title, count(mc.id) AS quantity
FROM mymyv_cards mc
WHERE mc.publicated = 1 AND STR_TO_DATE(substring(mc.publication_date, 1, 10), "%d-%m-%Y") BETWEEN CURDATE() - INTERVAL 30 DAY AND CURDATE()
GROUP BY substring(mc.publication_date, 1, 10)
And I get this:
But I would like the result was like:
06-04-2021 --> 1
07-04-2021 --> 2
08-04-2021 --> 0
09-04-2021 --> 0
10-04-2021 --> 0
............... etc etc
I don't know how to do that, can you help me?
What you need is a list of all the dates. If you don't have one handy, you can create on using a recursive CTE:
with recursive dates as (
select curdate() as dte, 1 as lev
union all
select dte - interval 1 day, lev + 1
from dates
where lev < 30
)
select *
from dates;
Then you incorporate this into your query with a left join:
with recursive dates as (
select curdate() as dte, 1 as lev
union all
select dte - interval 1 day, lev + 1
from dates
where lev < 30
)
select d.dte, count(mc.id)
from dates d left join
mymyv_cards mc
on mc.publicated = 1 and
str_to_date(left(mc.publication_date, 10), '%d-%m-%Y') >= d.dte and
str_to_date(left(mc.publication_date, 10), '%d-%m-%Y') < d.dte + interval 1 day
group by d.dte;
Note that a column called publication_date should be stored with the value as a date not as a string. You should really fix the data model so the data is stored using the correct types.
Also, you might have a numbers table or calendar table lying around in your database. If so, you can use that instead of the recursive CTE.

Combine 2 similar queries with different time ranges into 1 instead of using UNION ALL

I am using MySQL 5.7 and I need to do queries from a table like
order_id fee created_time
111 10 2020-11-16
222 90 2020-11-01
333 300 2000-10-22
The results should be the total income of last 1 day(yesterday) and last 30 days, like
date_range revenue
1 10
30 400
The column date_range is the last X day before now and I can do this use 'union all':
SELECT 1 AS date_range, SUM(fee) FROM test
WHERE created_time >= SUBDATE(CURRENT_DATE, 1) AND created_time < CURRENT_DATE
UNION ALL
SELECT 30 AS date_range, SUM(fee) FROM test
WHERE created_time >= SUBDATE(CURRENT_DATE, 30) AND created_time < CURRENT_DATE
The queries are quite similar and is it possible to combine them into ONE query instead of using union all?
CREATE TABLE:
CREATE TABLE test (
order_id INT,
fee INT,
created_time DATETIME
)
INSERT VALUES:
INSERT INTO test VALUES (111,10,'2020-11-16'),(222,90,'2020-11-01'),(333,300,'2020-10-22')
SELECT date_range, SUM(fee)
FROM test
CROSS JOIN (SELECT 1 date_range UNION ALL SELECT 30) date_ranges
WHERE created_time >= CURRENT_DATE - INTERVAL date_range DAY
AND created_time < CURRENT_DATE
GROUP BY date_range
UPDATE
You may improve the performance additionally while creating date-generated subquery with interval borders, not interval lengths:
SELECT DATEDIFF(CURRENT_DATE, date_range) date_range, SUM(fee)
FROM test
CROSS JOIN (SELECT CURRENT_DATE - INTERVAL 1 DAY date_range
UNION ALL
SELECT CURRENT_DATE - INTERVAL 30 DAY) date_ranges
WHERE created_time >= date_range
AND created_time < CURRENT_DATE
GROUP BY date_range
DEMO
You can try using case when:
https://dev.mysql.com/doc/refman/5.7/en/case.html
select date_range, sum(fee)
from (
select
case
when created_time between subdate(current_date, 1) and current_date then 1
when created_time between subdate(current_date, 30) and current_date then 30
end case date_range,
fee
from test) t
where date_range is not null
group by date_range

MySQL procedure keeps inserting replicated fields

This keeps inserting already existing fields although it shouldn't.
BEGIN
INSERT INTO ohrm_attendance_raw_data (punch_time, device_id, card_number)
SELECT punch_time, device_id, card_number
FROM ohrm_attendance_master
WHERE ohrm_attendance_master.punch_time >= DATE_SUB(now(), INTERVAL 1 MONTH)
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE ohrm_attendance_record.punch_in_user_time = ohrm_attendance_master.punch_time)
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE ohrm_attendance_record.punch_out_user_time = punch_time);
end
It looks like your field punch_time is datetime type or something like that... so I think your problem is that you are comparing two dates... and what's the problem with that?, that MySQL and other RDBMS compare that including hour, minutes, seconds and miliseconds... so it can make that the comparison be false... You can trunc the date or give it some format:
With DATE function:
BEGIN
INSERT INTO ohrm_attendance_raw_data (punch_time, device_id, card_number)
SELECT punch_time, device_id, card_number
FROM ohrm_attendance_master
WHERE ohrm_attendance_master.punch_time >= DATE_SUB(now(), INTERVAL 1 MONTH)
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE DATE(ohrm_attendance_record.punch_in_user_time) = DATE(ohrm_attendance_master.punch_time))
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE DATE(ohrm_attendance_record.punch_out_user_time) = DATE(punch_time));
END
With DATE_FORMAT function:
BEGIN
INSERT INTO ohrm_attendance_raw_data (punch_time, device_id, card_number)
SELECT punch_time, device_id, card_number
FROM ohrm_attendance_master
WHERE ohrm_attendance_master.punch_time >= DATE_SUB(now(), INTERVAL 1 MONTH)
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE DATE_FORMAT(ohrm_attendance_record.punch_in_user_time, '%d-%b-%Y') = DATE_FORMAT(ohrm_attendance_master.punch_time, '%d-%b-%Y'))
AND NOT EXISTS (
SELECT 1 FROM ohrm_attendance_record WHERE DATE_FORMAT(ohrm_attendance_record.punch_out_user_time, '%d-%b-%Y') = DATE_FORMAT(punch_time,'%d-%b-%Y'));
END

How to calculate number of Sundays between two dates using mysql

I need to find number of Sundays between two dates using mysql. I know how to do it using PHP But i need it to be calculated using mysql.
SET #START_DATE = '2014-01-22';
SET #END_DATE = '2014-06-29';
SELECT
ROUND((
(unix_timestamp(#END_DATE) - unix_timestamp(#START_DATE) ) /(24*60*60)
-7+WEEKDAY(#START_DATE)-WEEKDAY(#END_DATE)
)/7)
+ if(WEEKDAY(#START_DATE) <= 6, 1, 0)
+ if(WEEKDAY(#END_DATE) >= 6, 1, 0) as Sundays;
Solution consist of 2 parts:
counts number of full weeks (ROUND part), obviously you'll have 7 Sundays in 7 weeks.
count days that are included into the not full week parts (first or last)
If you'll need to use it more than once you can wrap it into the function:
DROP function IF EXISTS `count_weekdays`;
DELIMITER $$
CREATE FUNCTION `count_weekdays` (startDate date, endDate date, wd int)
RETURNS INTEGER
BEGIN
RETURN ROUND((
(unix_timestamp(endDate) - unix_timestamp(startDate) ) /(24*60*60)
-7+WEEKDAY(startDate)-WEEKDAY(endDate)
)/7)
+ if(WEEKDAY(startDate) <= wd, 1, 0)
+ if(WEEKDAY(endDate) >= wd, 1, 0);
END$$
DELIMITER ;
Then you will be able to use it like this:
SET #START_DATE = '2018-07-03';
SET #END_DATE = '2018-07-28';
select
count_weekdays(#START_DATE, #END_DATE, 6) as Sundays,
count_weekdays(#START_DATE, #END_DATE, 5)
+ count_weekdays(#START_DATE, #END_DATE, 6) as weekends;
Where 6 ~ number of weekday stands for Sunday, 5 ~ Saturday.
http://sqlfiddle.com/#!2/d41d8/50695
Maybe there will be no code on this link someday so I posted it here.
the upper link will show the number of the day and with date of the sundays. if you want to find any other day change the 1 at last to 2 for monday
select DATE_ADD('2012-12-15', INTERVAL ROW DAY) as Date,
row+1 as DayOfMonth from (
SELECT #row := #row + 1 as row FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6) t2,
(SELECT #row:=-1) t3 limit 31
) b
where
DATE_ADD('2012-12-01', INTERVAL ROW DAY)
between '2012-12-01' and '2012-12-31'
and
DAYOFWEEK(DATE_ADD('2012-12-01', INTERVAL ROW DAY))=1
Does this work...
SELECT FLOOR(
(DATEDIFF(
'#enddate'
,'#startdate' + INTERVAL
LENGTH(SUBSTRING_INDEX('654321',WEEKDAY('#startdate'),1))
DAY) + 1)/7) + 1 x;
SELECT a.StartDate, a.EndDate,
(DAY(EndDate - StartDate) / 7)
+ iif(DAY(EndDate - StartDate)%7 + DATEPART(DW, StartDate) > 8 , 1, 0)
Sundays FROM
(SELECT GETDATE() StartDate, DATEADD(DAY, 11, GETDATE()) EndDate) a
This is useful for multiple start and end dates in a table.
no need of variables and loops!
How it works: determines the number of full weeks between start and end date,
determines the days less than a week and add with the start day index(1,2,3,4,5,6,7) if that value Greater than count 8(means which is Sunday !)
For Sunday week day index is 1, day 8 is nothing but index 1.
SELECT COUNT(*) FROM table_name
WHERE column BETWEEN 'start_date' AND 'end_date'
AND WEEKDAY(date) = 6;
Refer this, mysql have a function dayofweek, you can easily calculate starting week day and after that divide the number of days with 7:
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_dayofweek
OR
SELECT COUNT(*) FROM table_name
WHERE date_column_of_table_name BETWEEN '2013-01-11' AND '2013-03-01'
AND WEEKDAY(date_column_of_table_name) = 6
Try this
select count(DAYNAME(your_date_field)='Sunday') as sunday from tbl_name where your_date_field between 'date1' AND 'date2'