MySQL stored procedure, while loop, union dataset - mysql

I'm new to stored procedures so I may be doing this wrong. I've created a stored procedure that brings back month name, month start and month end but I'm getting 12 separate datasets.
How can I combine this into one?
BEGIN
SET #i = 0;
SET #start_date = "2018-09-01";
SET #end_date = LAST_DAY('2018-09-01');
WHILE #i < 12 DO
SELECT MONTHNAME(#start_date) AS month, #start_date, #end_date;
SET #i = #i + 1;
SET #start_date = DATE_ADD(#start_date, INTERVAL 1 MONTH);
SET #end_date = LAST_DAY(DATE_ADD(#end_date, INTERVAL 1 MONTH));
END WHILE;
END

You don't need a stored procedure for that.
set #start_date = '2018-09-01';
with recursive months(m) as (
select 0
union all
select m + 1
from months
where m < 11
)
select #start_date + interval m.m month as first_day
, last_day(#start_date + interval m.m month) as last_day
from months m
order by m.m asc
Result:
first_day last_day
2018-09-01 2018-09-30
2018-10-01 2018-10-31
2018-11-01 2018-11-30
2018-12-01 2018-12-31
2019-01-01 2019-01-31
2019-02-01 2019-02-28
2019-03-01 2019-03-31
2019-04-01 2019-04-30
2019-05-01 2019-05-31
2019-06-01 2019-06-30
2019-07-01 2019-07-31
2019-08-01 2019-08-31
But you can ofcourse use that query in your SP too.

Rewrote #Paul Spiegel query so that I can understand how recursive queries work.
SET #start = "2018-09-01";
WITH RECURSIVE months (m)
AS (
SELECT 0
UNION ALL
SELECT m + 1
FROM months
WHERE m < 12
)
SELECT
MONTHNAME(DATE_ADD(#start, INTERVAL m MONTH)) AS month_name,
CONCAT(DATE_ADD(#start, INTERVAL m MONTH), " 00:00:00") AS start_of_month,
CONCAT(LAST_DAY(DATE_ADD(#start, INTERVAL m MONTH)), " 23:59:59") AS end_of_month
FROM months;

For MySQL versions before 8.0 , we can do something like this:
SELECT MONTHNAME(d.dt + INTERVAL i.n MONTH) AS month
, d.dt + INTERVAL i.n MONTH AS start_date
, LAST_DAY(d.dt + INTERVAL i.n MONTH) AS end_date
FROM ( SELECT '2018-09-01' + INTERVAL 0 MONTH AS dt ) d
CROSS
JOIN ( SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11
) i
ORDER
BY i.n

Related

Convert Month into weeks in MySQL

I am looking to breakdown a Month into weeks(starting Monday) in MySQL.
For example,
Sept 3 - 9th
Sept 10 - 16th
How to achieve this using MySql?
Thanks in advance.
This query will produce the results you want. It generates a table of possible week numbers in the month (0 to 4) using a UNION, and then it adds those week numbers to the computation of the first Monday of the month (stored in the variable #firstday which is JOINed to the table of week numbers).
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
JOIN (SELECT #firstday := FROM_DAYS(TO_DAYS(CURDATE())-DAY(CURDATE())+1) +
(7 - WEEKDAY(FROM_DAYS(TO_DAYS(CURDATE())-DAY(CURDATE())+1))) % 7) f
HAVING end <= LAST_DAY(#firstday)
Output:
start end
2018-09-03 2018-09-09
2018-09-10 2018-09-16
2018-09-17 2018-09-23
2018-09-24 2018-09-30
To run the query for any given month, replace CURDATE() in the computation of #firstday (4 places) with a date in the month you are interested in e.g.
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
JOIN (SELECT #firstday := FROM_DAYS(TO_DAYS('2018-01-20')-DAY('2018-01-20')+1) +
(7 - WEEKDAY(FROM_DAYS(TO_DAYS('2018-01-20')-DAY('2018-01-20')+1))) % 7) f
HAVING end <= LAST_DAY(#firstday)
Output:
start end
2018-01-01 2018-01-07
2018-01-08 2018-01-14
2018-01-15 2018-01-21
2018-01-22 2018-01-28
If you have the flexibility of setting variables, you can clean up the query like so:
SET #day = '2018-01-20';
SET #firstday := FROM_DAYS(TO_DAYS(#day)-DAY(#day)+1) + (7 - WEEKDAY(FROM_DAYS(TO_DAYS(#day)-DAY(#day)+1))) % 7;
SELECT #firstday + INTERVAL w WEEK AS start, #firstday + INTERVAL w WEEK + INTERVAL 6 DAY AS end
FROM (SELECT 0 AS w UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) weeks
HAVING end <= LAST_DAY(#firstday)
You can use function week(date) - https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_week

SQL query - counting what is not there [duplicate]

I have a database of users. I would like to create a graph based on userbase growth. The query I have now is:
SELECT DATE(datecreated), count(*) AS number FROM users
WHERE DATE(datecreated) > '2009-06-21' AND DATE(datecreated) <= DATE(NOW())
GROUP BY DATE(datecreated) ORDER BY datecreated ASC
This returns almost what I want. If we get 0 users one day, that day is not returned as a 0 value, it is just skipped and the next day that has at least one user is returned. How can I get something like (psuedo-response):
date1 5
date2 8
date3 0
date4 0
date5 9
etc...
where the dates with zero show up in sequential order with the rest of the dates?
Thanks!
I hope you will figure out the rest.
select * from (
select date_add('2003-01-01 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date from
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n1,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n2,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n3,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n4,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n5
) a
where date >'2011-01-02 00:00:00.000' and date < NOW()
order by date
With
select n3.num*100+n2.num*10+n1.num as date
you will get a column with numbers from 0 to max(n3)*100+max(n2)*10+max(n1)
Since here we have max n3 as 3, SELECT will return 399, plus 0 -> 400 records (dates in calendar).
You can tune your dynamic calendar by limiting it, for example, from min(date) you have to now().
This question asks the same thing I think. Generally the accepted answer seems to be that you either do it in your application logic (read in what you have into an array, then loop through the array and create the missing dates), or you use temporary tables filled with the dates you wish to join.
This is better to do as:
-- 7 Days:
set #n:=date(now() + interval 1 day);
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select (select #n:= #n - interval 1 day) day_series from tbl1 limit 7 ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 7 day) order by qb.day_series asc
-- 30 Days:
set #n:=date(now() + interval 1 day);
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select (select #n:= #n - interval 1 day) day_series from tbl1 limit 30 ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 30 day) order by qb.day_series asc;
or without variable like this:
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select curdate() - INTERVAL a.a day as day_series from(
select 0 as a union all select 1 union all select 2 union all
select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) as a ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 7 day) order by qb.day_series asc;
Do a right outer join to a table, call it tblCalendar, that is pre-populated with the dates you wish to report on. And join on the date field.
Paul
Query is:
SELECT qb.dy as yourday, COALESCE(count(yourcolumn), 0) as yourcount from yourtable qa
right join (
select curdate() as dy union
select DATE_SUB(curdate(), INTERVAL 1 day) as dy union
select DATE_SUB(curdate(), INTERVAL 2 day) as dy union
select DATE_SUB(curdate(), INTERVAL 3 day) as dy union
select DATE_SUB(curdate(), INTERVAL 4 day) as dy union
select DATE_SUB(curdate(), INTERVAL 5 day) as dy union
select DATE_SUB(curdate(), INTERVAL 6 day) as dy
) as qb
on qa.dates = qb.dy
and qa.dates > DATE_SUB(curdate(), INTERVAL 7 day)
order by qb.dy asc;
and the result is:
+------------+-----------+
| yourday | yourcount |
+------------+-----------+
| 2015-06-24 | 274339 |
| 2015-06-25 | 0 |
| 2015-06-26 | 0 |
| 2015-06-27 | 0 |
| 2015-06-28 | 134703 |
| 2015-06-29 | 87613 |
| 2015-06-30 | 0 |
+------------+-----------+
On further thought, something like this should be what you want:
CREATE TEMPORARY TABLE DateSummary1 ( datenew timestamp ) SELECT DISTINCT(DATE(datecreated)) as datenew FROM users;
CREATE TEMPORARY TABLE DateSummary2 ( datenew timestamp, number int ) SELECT DATE(datecreated) as datenew, count(*) AS number FROM users
WHERE DATE(datecreated) > '2009-06-21' AND DATE(datecreated) <= DATE(NOW())
GROUP BY DATE(datecreated) ORDER BY datecreated ASC;
SELECT ds1.datenew,ds2.number FROM DateSummary1 ds1 LEFT JOIN DateSummary2 ds2 on ds1.datenew=ds2.datenew;
This gives you all the dates in the first table, and the count summary data in the second table. You might need to replace ds2.number with IF(ISNULL(ds2.number),0,ds2.number) or something similar.

How to get the all mondays in a month?

I want to get all the Mondays in the month of MAY 2015
(using mysql query)
OUTPUT:
MON
04
11
18
25
select row+1 as Mon 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('2015-05-01', INTERVAL ROW DAY) between '2015-05-01' and '2015-05-31' and DAYOFWEEK(DATE_ADD('2015-05-01', INTERVAL ROW DAY))=2;
Output
+------------+
| Mon |
+------------+
| 4 |
| 11 |
| 18 |
| 25 |
+------------+
Tweaking a bit this query
For reference, here's another solution - note that the last entry may be null, can be changed to another value if necessary, or wrap in a sub-select and filter on not null.
SET #date='2015-05-01';
SET #offset=7 - WeekDay(#date);
SELECT DAY(DATE_ADD(#date,INTERVAL #offset DAY)) AS 'MON'
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+7 DAY))
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+14 DAY))
UNION SELECT DAY(DATE_ADD(#date,INTERVAL #offset+21 DAY))
UNION DISTINCT SELECT IF(DAY(DATE_ADD(#date,INTERVAL #offset+28 DAY))>21,
DAY(DATE_ADD(#date,INTERVAL #offset+28 DAY)),
DAY(DATE_ADD(#date,INTERVAL #offset+21 DAY)))
;
SQL Fiddle: http://sqlfiddle.com/#!9/fa4ce/4
This query returns the two digit day value of the Mondays in a month.
This requires the "month" as a date of the first day of the month, as a value in the SELECT list of the first inline view (d0). (This inline view query could be tweaked to handle any date value within a month as the specification for a month.)
SELECT DATE_FORMAT(d0.dt + INTERVAL d1.i*6+d2.i DAY,'%d') AS dd
-- , d0.dt + INTERVAL d1.i*6+d2.i DAY AS dt
FROM ( SELECT '2015-05-01' + INTERVAL 0 DAY AS dt
) d0
CROSS
JOIN ( SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
) d1
CROSS
JOIN ( SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
) d2
WHERE d0.dt + INTERVAL d1.i*6+d2.i DAY < d0.dt + INTERVAL 1 MONTH
AND NOT WEEKDAY(d0.dt + INTERVAL d1.i*6+d2.i DAY)
ORDER BY 1
NOTE: This requires the month to be specified only once, in the first inline view (d0). Everything else is handled in expression that reference this one value.
The WEEKDAY function returns 0 for a date value that is a Monday, so a NOT on the return from the WEEKDAY function will return TRUE for a Monday.
For a supplied date value of '2015-05-01', this returns:
dd
--
04
11
18
25

How to skip the column if dayname equals to Saturday or Sunday in Mysql query

I just want to show the dates, suppose if i select a date of 1st dec 2013 then we need to show next 7 days like
2013-12-01 2013-12-02 2013-12-03 2013-12-04 2013-12-05 2013-12-06 2013-12-07
Here is mysql query
select
cast('2013-12-01' as date) AS `1`,
(cast('2013-12-01' as date) + interval 1 day) AS `2`,
(cast('2013-12-01' as date) + interval 2 day) AS `3`,
(cast('2013-12-01' as date) + interval 3 day) AS `4`,
(cast('2013-12-01' as date) + interval 4 day) AS `5`,
(cast('2013-12-01' as date) + interval 5 day) AS `6`,
(cast('2013-12-01' as date) + interval 6 day) AS `7`,
(cast('2013-12-01' as date) + interval 7 day) AS `8`,
(cast('2013-12-01' as date) + interval 8 day) AS `9`,
(cast('2013-12-01' as date) + interval 9 day) AS `10`,
(cast('2013-12-01' as date) + interval 10 day) AS `11`
It gives me all days upto 2013-02-11 but I want to skip those columns whose dayname = "Saturday" or "Sunday", how i can solve this.
I tried alot but failed.Hope you help me.
Thanks in advance.
Something like this:
select GROUP_CONCAT(dt)
FROM
(
select
(cast('2013-12-01' as date) + interval t.n day) dt
FROM
(
select 0 as n union all
select 1 as n union all
select 2 as n union all
select 3 as n union all
select 4 as n union all
select 5 as n union all
select 6 as n union all
select 7 as n union all
select 8 as n union all
select 9 as n union all
select 10 as n union all
select 11 as n union all
select 12 as n union all
select 13 as n) t
WHERE WEEKDAY((cast('2013-12-01' as date) + interval t.n day)) not in (5,6)
ORDER BY dt
LIMIT 7
) t1
SQLFiddle demo

MySQL: Select All Dates In a Range Even If No Records Present

I have a database of users. I would like to create a graph based on userbase growth. The query I have now is:
SELECT DATE(datecreated), count(*) AS number FROM users
WHERE DATE(datecreated) > '2009-06-21' AND DATE(datecreated) <= DATE(NOW())
GROUP BY DATE(datecreated) ORDER BY datecreated ASC
This returns almost what I want. If we get 0 users one day, that day is not returned as a 0 value, it is just skipped and the next day that has at least one user is returned. How can I get something like (psuedo-response):
date1 5
date2 8
date3 0
date4 0
date5 9
etc...
where the dates with zero show up in sequential order with the rest of the dates?
Thanks!
I hope you will figure out the rest.
select * from (
select date_add('2003-01-01 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date from
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n1,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n2,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n3,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n4,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n5
) a
where date >'2011-01-02 00:00:00.000' and date < NOW()
order by date
With
select n3.num*100+n2.num*10+n1.num as date
you will get a column with numbers from 0 to max(n3)*100+max(n2)*10+max(n1)
Since here we have max n3 as 3, SELECT will return 399, plus 0 -> 400 records (dates in calendar).
You can tune your dynamic calendar by limiting it, for example, from min(date) you have to now().
This question asks the same thing I think. Generally the accepted answer seems to be that you either do it in your application logic (read in what you have into an array, then loop through the array and create the missing dates), or you use temporary tables filled with the dates you wish to join.
This is better to do as:
-- 7 Days:
set #n:=date(now() + interval 1 day);
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select (select #n:= #n - interval 1 day) day_series from tbl1 limit 7 ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 7 day) order by qb.day_series asc
-- 30 Days:
set #n:=date(now() + interval 1 day);
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select (select #n:= #n - interval 1 day) day_series from tbl1 limit 30 ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 30 day) order by qb.day_series asc;
or without variable like this:
SELECT qb.day_series as days , COALESCE(col_byte, 0) as Bytes from tbl1 qa
right join (
select curdate() - INTERVAL a.a day as day_series from(
select 0 as a union all select 1 union all select 2 union all
select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) as a ) as qb
on date(qa.Timestamp) = qb.day_series and
qa.Timestamp > DATE_SUB(curdate(), INTERVAL 7 day) order by qb.day_series asc;
Do a right outer join to a table, call it tblCalendar, that is pre-populated with the dates you wish to report on. And join on the date field.
Paul
Query is:
SELECT qb.dy as yourday, COALESCE(count(yourcolumn), 0) as yourcount from yourtable qa
right join (
select curdate() as dy union
select DATE_SUB(curdate(), INTERVAL 1 day) as dy union
select DATE_SUB(curdate(), INTERVAL 2 day) as dy union
select DATE_SUB(curdate(), INTERVAL 3 day) as dy union
select DATE_SUB(curdate(), INTERVAL 4 day) as dy union
select DATE_SUB(curdate(), INTERVAL 5 day) as dy union
select DATE_SUB(curdate(), INTERVAL 6 day) as dy
) as qb
on qa.dates = qb.dy
and qa.dates > DATE_SUB(curdate(), INTERVAL 7 day)
order by qb.dy asc;
and the result is:
+------------+-----------+
| yourday | yourcount |
+------------+-----------+
| 2015-06-24 | 274339 |
| 2015-06-25 | 0 |
| 2015-06-26 | 0 |
| 2015-06-27 | 0 |
| 2015-06-28 | 134703 |
| 2015-06-29 | 87613 |
| 2015-06-30 | 0 |
+------------+-----------+
On further thought, something like this should be what you want:
CREATE TEMPORARY TABLE DateSummary1 ( datenew timestamp ) SELECT DISTINCT(DATE(datecreated)) as datenew FROM users;
CREATE TEMPORARY TABLE DateSummary2 ( datenew timestamp, number int ) SELECT DATE(datecreated) as datenew, count(*) AS number FROM users
WHERE DATE(datecreated) > '2009-06-21' AND DATE(datecreated) <= DATE(NOW())
GROUP BY DATE(datecreated) ORDER BY datecreated ASC;
SELECT ds1.datenew,ds2.number FROM DateSummary1 ds1 LEFT JOIN DateSummary2 ds2 on ds1.datenew=ds2.datenew;
This gives you all the dates in the first table, and the count summary data in the second table. You might need to replace ds2.number with IF(ISNULL(ds2.number),0,ds2.number) or something similar.