MySQL需借助数字表或CTE生成连续日期序列:有numbers表时用DATE_ADD+DATEDIFF;无表时MySQL 8.0+可用WITH RECURSIVE构造seq CTE生成0起递增数字,再转为日期。

用 MySQL 生成指定日期范围的连续日期序列
MySQL 本身没有内置的日期序列生成函数(不像 PostgreSQL 的 generate_series()),所以得靠辅助表或变量/CTE 模拟。最稳定、兼容性最好的方式是关联一个「连续数字表」(通常叫 numbers 或 seq),再用 DATE_ADD() 偏移出日期。
关键点:这个数字表必须足够大,覆盖你最大可能的日期跨度(比如 1000 天 → 至少需要 0~999 共 1000 行)。
- 若已有
numbers表(单列n,从 0 开始连续整数),直接用:SELECT DATE_ADD('2024-01-01', INTERVAL n DAY) AS dt FROM numbers WHERE n BETWEEN 0 AND DATEDIFF('2024-01-10', '2024-01-01'); - 若没有,临时用 CTE(MySQL 8.0+)快速构造(不建表):
WITH RECURSIVE seq(n) AS ( SELECT 0 UNION ALL SELECT n + 1 FROM seq WHERE n < DATEDIFF('2024-01-10', '2024-01-01') ) SELECT DATE_ADD('2024-01-01', INTERVAL n DAY) AS dt FROM seq; - MySQL 5.7 及更早?只能建物理
numbers表,或用JOIN多张小表拼出大范围(如 cross jointens×units)
LEFT JOIN 找出业务表中缺失的日期
有了完整日期序列后,和你的业务表(比如 orders,含 order_date 字段)做 LEFT JOIN,就能筛出没数据的日期。
注意:JOIN 条件必须严格匹配日期(不是 datetime),否则时分秒会导致关联失败;建议统一用 DATE() 或提前在业务表里存日期类型字段。
- 假设业务表是
sales,日期字段为sale_date(类型为DATE):WITH RECURSIVE seq(n) AS ( SELECT 0 UNION ALL SELECT n + 1 FROM seq WHERE n < DATEDIFF('2024-01-05', '2024-01-01') ) SELECT full_dates.dt FROM ( SELECT DATE_ADD('2024-01-01', INTERVAL n DAY) AS dt FROM seq ) AS full_dates LEFT JOIN sales ON full_dates.dt = sales.sale_date WHERE sales.sale_date IS NULL; - 如果
sale_date是DATETIME,JOIN 条件要写成:DATE(sales.sale_date) = full_dates.dt—— 但注意这会让索引失效,性能差;更优解是业务表加生成列或冗余sale_date_only DATE AS (DATE(sale_date)) STORED并建索引
为什么不能直接用 NOT IN 或 NOT EXISTS?
看起来简单,但 NOT IN (SELECT ...) 在业务表存在 NULL 值时会整个返回空结果集 —— 这是 SQL 三值逻辑的经典陷阱,极易踩坑且难排查。
-
NOT IN等价于!= ALL,而任何值与NULL比较结果都是UNKNOWN,导致整个条件不成立 -
NOT EXISTS虽安全,但子查询需关联外层,写法稍繁,且对大范围日期扫描效率不如预生成序列 + LEFT JOIN 清晰可控 - LEFT JOIN 方式逻辑直白、可读性强,执行计划也更容易预测(尤其当日期范围固定时)
性能与边界细节容易被忽略
生成日期序列本身很快,但真正卡顿往往出现在 JOIN 阶段 —— 尤其当业务表没索引、或用了 DATE() 函数导致无法走索引时。
- 务必确认业务表的日期字段有索引;如果是
DATETIME,优先建函数索引(MySQL 8.0.13+):CREATE INDEX idx_sale_date ON sales (DATE(sale_date)) - 日期范围别过大:生成 10 年序列(3652 行)没问题,但生成 100 年(36524 行)再 JOIN 百万级订单表,内存和临时表压力会明显上升
- CTE 递归深度默认是 1000,超限时需设
SET SESSION cte_max_recursion_depth = 10000(仅会话级,不影响全局)
实际用的时候,先跑通小范围(比如 7 天),再扩到目标区间;只要数字表或 CTE 步长和起止算对,缺失日期就稳了。


















