Generating a range of dates in MySQL
It’s often useful to have a range of contiguous dates when building SQL queries, for example by using a left join to ensure that data from other tables that might have missing dates will get a null value in those rows instead of skipping the rows entirely.
Postgres has its wonderful generate_series function which can solve a lot of these problems. Unfortunately MySQL doesn’t have the same function.
You can generate a range of contiguous dates in MySQL with a query like this:
SELECT date_series.dates
FROM (
SELECT curdate() - INTERVAL (units.mul + (10 * tens.mul) + (100 * hundreds.mul) + (1000 * thousands.mul)) DAY AS dates
FROM (SELECT 0 AS mul 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) AS units
CROSS JOIN (SELECT 0 AS mul 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) AS tens
CROSS JOIN (SELECT 0 AS mul 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) AS hundreds
CROSS JOIN (SELECT 0 AS mul 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) AS thousands
) date_series
That will produce 10000 dates like this:
+----------+
|dates |
+----------+
|2021-08-23|
|2021-08-22|
|2021-08-21|
|2021-08-20|
|2021-08-19|
|2021-08-18|
|2021-08-17|
|2021-08-16|
|2021-08-15|
|2021-08-14|
| ... |
+----------+
This looks a little crazy at first, but it’s reasonable once you take a look through it. It uses a series of cross joins to build up decimal positions with values from 0 through to 9, for the units, tens, hundreds and thousands positions. It then subtracts each resulting number as a number of days from the current date to produce the range of contiguous dates that we want.
This query uses curdate() - INTERVAL
to generate dates going back in time from
today. You can swap curdate()
for another date to have a different starting
point, or do + INTERVAL
to generate dates going forwards from the initial
date.
This query produces a range of 10000 dates, which is about 27 years and probably
sufficient for most purposes. You can adjust the range with a WHERE
clause to
suit different needs, e.g.:
SELECT a.dates
FROM (
SELECT curdate() - INTERVAL (units.mul + (10 * tens.mul) + (100 * hundreds.mul) + (1000 * thousands.mul)) DAY AS dates
FROM (SELECT 0 AS mul 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) AS units
CROSS JOIN (SELECT 0 AS mul 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) AS tens
CROSS JOIN (SELECT 0 AS mul 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) AS hundreds
CROSS JOIN (SELECT 0 AS mul 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) AS thousands
) a
WHERE a.dates >= curdate() - INTERVAL 1 YEAR
AND DAYNAME(a.dates) = 'Monday'
That limits the date range to dates up to a year ago, and only Mondays:
+----------+
|dates |
+----------+
|2021-08-23|
|2021-08-16|
|2021-08-09|
|2021-08-02|
|2021-07-26|
|2021-07-19|
|2021-07-12|
|2021-07-05|
|2021-06-28|
|2021-06-21|
| ... |
+----------+
By the way, if you need help with MySQL or SQL in your business, you can hire me as a freelance SQL developer.