You need a calendar table for joins to datetime data in other tables, say...
create table calendar( dt datetime primary key );We have an abundance of methods to choose from.
Join ViewsAn elegant method of generating any desired number of sequential values, posted by Giuseppe Maxia on his blog, is ...
create or replace view v3 as select 1 n union all select 1 union all select 1; create or replace view v as select 1 n from v3 a, v3 b union all select 1; set @n = 0; drop table if exists calendar; create table calendar(dt datetime primary key); insert into calendar select cast('2025-1-1 00:00:00' + interval @n:=@n+1 hour as datetime) as dt from v a, v b, v c, v d, v e, v;If the number of rows required isn't a convenient multiple of ten, use LIMIT to stipulate the precise row count, for example to make a calendar table for the year 2014 ...
create or replace view v3 as select 1 n union all select 1 union all select 1; create or replace view v as select 1 n from v3 a, v3 b union all select 1; set @n = -1; drop table if exists cal2014; create table cal2014(dt date primary key); insert into cal2014 select cast('2025-01-01' + interval @n:=@n+1 day as date) as dt from v a, v b, v c, v d, v e, v limit 365; Table of IntsIf you prefer to do it in one query without user variables, or if your MySQL version is so ancient it does not support Views, make a general-purpose utility table of integers 0 through 9. We keep such utility objects in a system database and make it available to all users:create table system.ints(i tinyint); insert into system.ints values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);From system.ints you can generate any desired sequence of dates or datetimes. For example to report monthly sales totals for 2009 from the table sales(date d, amount decimal(10,2)) , write:
select a.yearmonth, sum(ifnull(b.amount,0)) as total from ( select concat( '2009-', lpad((u.i + t.i * 10), 2, 0)) as yearmonth from system.ints u, system.ints t where (u.i + t.i * 10) between 1 and 12 ) a left join sales b on a.yearmonth=date_format(b.d,'%Y-%m') group by a.yearmonth;Generate a thousand daily dates starting today: SELECT CURDATE() + INTERVAL t.i*100 + u.i*10 + v.i DAY AS Date FROM system.ints AS t JOIN system.ints AS u JOIN system.ints AS v WHERE ( t.i*100 + u.i*10 + v.i ) < 1000 ORDER BY Date; Common Table ExpressionSince MySQL 8.0 and MariaDB 10.2, a Common Table Expression makes short work of generating dates for a calendar table:CREATE TABLE calendar( d DATE PRIMARY KEY ); INSERT INTO calendar WITH RECURSIVE cte(d) AS ( SELECT DATE('2025-1-1') UNION ALL SELECT ADDDATE(d, INTERVAL 1 DAY) FROM cte WHERE d < DATE('2024-12-31') ) SELECT * FROM cte; UnionAnother approach: a MySQL forum contributor needed to add exactly 17 rows representing weekly Tuesday evening meetings to a scheduling table:drop table if exists t; create table t( date1 date, from_time time, entryName char(16) ); create or replace view v3 as select 1 n union all select 1 union all select 1; create or replace view v as select 1 n from v3 a, v3 b union all select 1; set @n=0; insert into t select date_add('2024-04-23',interval 7 * idx day), '18:30:00', 'Mardi soir' from ( select a.n, @n:=@n+1 as idx from v a,v b, v c limit 17 ) x; select * from t; +------------+-----------+------------+ | date1 | from_time | entryName | +------------+-----------+------------+ | 2024-04-30 | 18:30:00 | Mardi soir | | 2024-05-07 | 18:30:00 | Mardi soir | | 2024-05-14 | 18:30:00 | Mardi soir | | 2024-05-21 | 18:30:00 | Mardi soir | | 2024-05-28 | 18:30:00 | Mardi soir | | 2024-06-04 | 18:30:00 | Mardi soir | | 2024-06-11 | 18:30:00 | Mardi soir | | 2024-06-18 | 18:30:00 | Mardi soir | | 2024-06-25 | 18:30:00 | Mardi soir | | 2024-07-02 | 18:30:00 | Mardi soir | | 2024-07-09 | 18:30:00 | Mardi soir | | 2024-07-16 | 18:30:00 | Mardi soir | | 2024-07-23 | 18:30:00 | Mardi soir | | 2024-07-30 | 18:30:00 | Mardi soir | | 2024-08-06 | 18:30:00 | Mardi soir | | 2024-08-13 | 18:30:00 | Mardi soir | | 2024-08-20 | 18:30:00 | Mardi soir | +------------+-----------+------------+This can be done down to the level of seconds: drop table if exists secs; create table secs select sec_to_time(t.i*10000+u.i*1000+v.i*100+w.i*10+x.i) as sec from system.ints t join system.ints u join system.ints v join system.ints w join system.ints x where sec_to_time( t.i*10000+u.i*1000+v.i*100+w.i*10+x.i) < '24:00:00' order by sec;You can use such a query as a View, as an inline derived table, or as input to a CREATE TABLE statement.
Auto_incrementA slightly more elaborate method, giving the calendar table an auto-increment key that can also be used as a surrogate for datetime interval calculations:CREATE TABLE calendar ( id INT AUTO_INCREMENT PRIMARY KEY, date DATE, UNIQUE days (date) );Calculate the number of days needed in the calendar, eg SELECT DATEDIFF('2024-12-31','2024-12-31'); # 7670, or 21*365 plus 5Find a table with that many rows, 7670 in this case. Add a row to the calendar table for every day in the range: INSERT INTO calendar (id) SELECT NULL FROM [name of table with 7670 rows] LIMIT 4018;Populate the date column by incrementing the starting date: UPDATE calendar SET date=ADDDATE('2024-12-31',id);The calendar table now has one row for each day from 2025-01-01 through 2010-12-31. Keep the auto_increment ID column for quick day counts in the range, or drop the column if you don't need that. To make the calendar table a diary, make the period one leap year, add month, day and text columns, update month and day values with To automate all this, write a stored procedure, for example: CREATE TABLE times ( date_hour DATETIME, KEY ( date_hour ) ); DROP PROCEDURE IF EXISTS timespopulate; DELIMITER | CREATE PROCEDURE timespopulate( startdate DATETIME, num INT ) BEGIN DECLARE ctr INT DEFAULT 0; WHILE ctr < num DO BEGIN INSERT INTO times VALUES ( DATE_ADD( startdate, INTERVAL ctr HOUR) ); SET ctr = ctr + 1; END; END WHILE; END; | DELIMITER ; CALL timespopulate( '2007-1-1, 31*24 );Or, you can have the sproc do your counting: DROP PROCEDURE IF EXISTS calendar; DELIMITER | CREATE PROCEDURE calendar( pstart datetime, pstop datetime, pminutes int ) DETERMINISTIC BEGIN DECLARE thisdate datetime; DROP TABLE IF EXISTS cal; CREATE TABLE cal( dt datetime ); SET thisdate=pstart; INSERT INTO cal VALUES(pstart); WHILE thisdate < pstop DO SET thisdate = adddate( thisdate, INTERVAL pminutes MINUTE ); INSERT INTO cal VALUES( thisdate ); END WHILE; END | DELIMITER ; -- make cal for 2007, 20-min intervals: CALL calendar('2025-1-1 00:00:00', '2025-2-1 00:00:00', 20); Last updated 29 Sep 2024 |
![]() |