“连续3天达标”的本质是日期构成等差数列,通过ROW_NUMBER()与日期转整数后的差值生成group_key,相同group_key即为同一连续段,再按emp_id和group_key分组统计天数≥3。

什么是“连续3天达标”的数据特征
连续性判断本质是检查日期是否构成等差数列。在SQL中,不能直接对日期做“相邻”标记,必须借助窗口函数生成可比较的序号:用 ROW_NUMBER() 按员工分组、按日期排序得到逻辑序号,再用日期本身的 TO_DAYS()(MySQL)或 DATE_PART('day', ...)(PostgreSQL)转为整数天数,二者相减后相同值即代表连续日期。
注意:不同数据库日期转整数的方式不同,MySQL常用 TO_DAYS(date_col),PostgreSQL需用 (date_col - '1970-01-01'::DATE) 或 EXTRACT(EPOCH FROM date_col)::INT / 86400;SQLite 用 julianday(date_col)。统一用整数差才能可靠分组。
用 ROW_NUMBER() + 日期差构造连续段标识
核心思路是:对每个员工,把每天的 date_col 转成整数天数,减去按时间顺序排的行号,结果相同的记录就属于同一连续段。
SELECT emp_id, date_col, -- 关键:构造连续段标识 group_key TO_DAYS(date_col) - ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY date_col) AS group_key FROM attendance WHERE status = '达标';
这个 group_key 是临时标签,不是真实日期,但能稳定聚合同一段连续日期。后续只需按 emp_id 和 group_key 分组,统计 COUNT(*) >= 3 即可。
过滤出至少含3天的连续段并去重员工
直接在上一步结果外层嵌套聚合,避免重复返回同一员工的多个连续段:
SELECT DISTINCT emp_id
FROM (
SELECT
emp_id,
TO_DAYS(date_col) - ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY date_col) AS group_key
FROM attendance
WHERE status = '达标'
) t
GROUP BY emp_id, group_key
HAVING COUNT(*) >= 3;
- 必须用
DISTINCT,否则一个员工若有两段连续4天,会返回两次 -
HAVING是对窗口后分组的筛选,不能写成WHERE - 如果还需返回起止日期,可在内层加
MIN(date_col)和MAX(date_col)
常见坑:NULL、跨年、时区和非工作日干扰
- 员工某天无记录 ≠ 未达标,
status = '达标' 是显式条件,缺失记录不会参与计算,这点反而有利
- 跨年不影响
TO_DAYS() 或 julianday(),它们本就是绝对天数
- 若业务中“连续”指自然日(含周末),当前逻辑正确;若只算工作日,必须先补全所有工作日记录(用日历表LEFT JOIN),再判断
- MySQL 5.7 不支持窗口函数,必须升级到 8.0+;PostgreSQL 8.4+、SQL Server 2005+、Oracle 10g+ 均支持,但语法微调(如 SQL Server 用
DATEDIFF(day, '1900-01-01', date_col))
status = '达标' 是显式条件,缺失记录不会参与计算,这点反而有利TO_DAYS() 或 julianday(),它们本就是绝对天数DATEDIFF(day, '1900-01-01', date_col))连续性识别真正难的不是SQL写法,而是明确“连续”的业务定义——是否跳过节假日?是否允许同日多条记录?这些不厘清,ROW_NUMBER() 就会算错序号。

















