LEFT JOIN补全日期序列最直接识别连续缺失日期,先构造完整日期范围再筛选业务表无匹配的日期,需确保类型对齐并用WHERE右表字段IS NULL。

用 LEFT JOIN 补全日期序列再查空值最直接
识别连续缺失的日期,本质是先构造一个完整日期范围,再找出业务表里没匹配上的那些天。硬写 NOT IN 或 NOT EXISTS 容易因 NULL 值返回空结果,不推荐。
- 必须用
LEFT JOIN,让生成的日期序列做主表(驱动表),业务表做右表 - JOIN 条件要对齐类型:如果业务表字段是
DATETIME,得用DATE(sale_time)或提前建生成列;直接sale_time = dt会因时分秒不匹配而漏掉 - 最后加
WHERE 业务表.date_col IS NULL才能筛出真正缺失的日期 - 示例(MySQL 8.0+):
WITH RECURSIVE dates(dt) AS ( SELECT '2026-07-01'::DATE UNION ALL SELECT dt + INTERVAL 1 DAY FROM dates WHERE dt < '2026-07-15' ) SELECT d.dt FROM dates d LEFT JOIN sales s ON DATE(s.created_at) = d.dt WHERE s.created_at IS NULL;
ROW_NUMBER() 差值法只适合查“断点”,不适用查连续缺失区间
有人想复用识别 ID 缺号的 id - ROW_NUMBER() 方法来查日期缺失,但日期本身不是严格递增整数,不能直接套用——它只能告诉你“哪里断了”,无法回答“断了几天”或“哪几日连续缺失”。
- 日期列必须先转成序号(比如用
DATEDIFF(dt, '1970-01-01')),再算差值,否则类型不兼容 - 即使转成整数,
dt - ROW_NUMBER()相同只表示“逻辑连续”,不代表物理日期连续(比如跨月时可能因月末天数不同导致差值跳变) - 这个方法对单个用户/设备的登录连续性有效,但面对全局日期缺失统计,还是生成序列更稳妥
多维度分组下连续缺失必须先 CROSS JOIN 再 LEFT JOIN
如果你要查的是“每个用户在某段时间内各自缺失哪些日期”,就不能只生成日期序列然后左连——那样只会补全整体日期,不会补全“某用户某天完全没记录”的情况。
- 正确顺序是:先取所有唯一用户(或状态、品类等维度)× 日期序列 → 得到全组合 → 再左连原始事实表
- 否则,
LEFT JOIN后仍会漏掉“张三在7月5日无数据、李四在7月8日无数据”这类细粒度缺失 - 示例片段:
SELECT u.user_id, d.dt FROM (SELECT DISTINCT user_id FROM sales) u CROSS JOIN dates d LEFT JOIN sales s ON u.user_id = s.user_id AND DATE(s.created_at) = d.dt WHERE s.created_at IS NULL;
MySQL 8.0+ 递归 CTE 的深度限制必须显式处理
递归 CTE 看起来简洁,但 MySQL 默认 cte_max_recursion_depth = 1000,查跨度超 1000 天会直接报错 Recursive query aborted after 1000 iterations,不是慢,是中断。
- 临时解法:执行前加
SET SESSION cte_max_recursion_depth = 3000;(注意是 SESSION 级,不是 GLOBAL) - 长期建议:跨度大或频繁使用时,建一张永久
calendar表(含date字段,索引好),比每次递归更稳 - 别依赖
generate_series:MySQL 没这函数,PostgreSQL 才有;SQL Server 要借master..spt_values,上限仅 2047
实际写的时候,最容易被忽略的是日期类型对齐和多维补全的先后顺序——类型不对,JOIN 就失效;维度不全,漏掉的就是真实业务缺口。

















