直接套用CASE WHEN+ROW_NUMBER()会算错连续天数,因ROW_NUMBER()仅按行序编号,不反映真实日期间隔;正确方法是用DATEDIFF或TO_DAYS减去分组内行号生成稳定组键,再分组计数。

为什么直接套用 CASE WHEN + ROW_NUMBER() 会算错连续天数
很多人一上来就写:ROW_NUMBER() OVER (ORDER BY login_date) 减去 login_date,再按差值分组——这在日期连续但数据**缺失某天**(比如用户某天没登录)时完全失效。因为 ROW_NUMBER() 只管行序,不管真实日期间隔。真正要锚定的是「自然日递增」,不是“第几条记录”。
用 DATEDIFF 构造连续性标识(核心技巧)
关键不是给每行编号,而是把「连续签到」转化为「日期与基准日的差值是否恒定」。常用做法是:用当前日期减去按日期排序的行号,得到一个“组键”。只要这个组键相同,说明这些日期是连续的。
- 先按
user_id分组,再按login_date排序,生成ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) - 把
login_date转成天数(如用TO_DAYS(login_date)或DATEDIFF(login_date, '1970-01-01')),再减去上面的行号 - 这个差值就是连续段的唯一标识,相同值的行属于同一连续签到段
示例(MySQL):
SELECT
user_id,
login_date,
COUNT(*) OVER (PARTITION BY user_id, grp) AS consecutive_days
FROM (
SELECT
user_id,
login_date,
TO_DAYS(login_date) - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS grp
FROM user_login
) t;
CASE WHEN 在窗口函数外做逻辑分流才安全
CASE WHEN 不能直接嵌在窗口函数内部去“条件计算连续天数”,比如 COUNT(CASE WHEN ... THEN 1 END) OVER (...) 会破坏连续性分组逻辑。它只适合在窗口结果出来后,对已算出的 consecutive_days 做业务判断。
- 想标出“是否达成7天连续签到”?用
CASE WHEN consecutive_days >= 7 THEN 'YES' ELSE 'NO' - 想只保留每段连续签到的最后一条记录?加
ROW_NUMBER() OVER (PARTITION BY user_id, grp ORDER BY login_date DESC) = 1 - 注意:所有
CASE WHEN必须放在窗口计算完成之后,否则无法引用consecutive_days这类派生列
不同数据库的日期处理陷阱
PostgreSQL 没有 TO_DAYS,得用 login_date - INTERVAL '1 day' * ROW_NUMBER()...;SQL Server 要用 DATEDIFF(day, '1900-01-01', login_date);而 BigQuery 需用 DATE_DIFF(login_date, DATE('1970-01-01'), DAY)。行号必须用 ROW_NUMBER(),不能用 RANK() 或 DENSE_RANK()——它们遇并列会跳号,彻底打乱差值一致性。
连续签到的本质是「日期序列的完整性校验」,不是排序或计数。差值法看着绕,但它把时间维度硬编码进数值里,才是最稳的锚点。










