系统状态的连续日期段:日期减行号找区间
LeetCode 1225 · 连续区间(gaps and islands)、UNION ALL、行号差
面试怎么回答
经典的 gaps and islands。把失败、成功两张表 UNION ALL 成(日期, 状态),过滤到 2019 年;再按状态分区用 DATE_SUB(日期, INTERVAL ROW_NUMBER() 天) 得到区间编号,连续日期的编号相同;最后按状态和编号分组取 MIN、MAX 日期。连续登录、连续打卡都是这个套路。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
题意
失败表记录任务失败的日期,成功表记录任务成功的日期,系统每天恰好运行一次。只看 2019 年,把连续相同状态的日期合并成区间,输出状态、开始日期和结束日期,按开始日期排序。
表结构
- Failed:fail_date(主键)
- Succeeded:success_date(主键)
思路
先把两张表合成一张「日期 + 状态」的流水(UNION ALL,并只保留 2019 年)。然后按状态分区、日期排序编号,用「日期减去行号」:同一状态下连续的日期,减去连续的行号后得到同一个值,这个值就是区间编号。最后按(状态, 区间编号)分组,取最小和最大日期。
参考解法
select type period_state, min(date) start_date, max(date) as end_date
from
(
select type, date, subdate(date,row_number()over(partition by type order by date)) as diff
from
(
select 'failed' as type, fail_date as date from Failed
where fail_date between '2019-01-01' and '2019-12-31'
union all
select 'succeeded' as type, success_date as date from Succeeded
where success_date between '2019-01-01' and '2019-12-31'
) a
)b
group by type,diff
order by start_date
易错点
- 过滤 2019 年的条件要在编号之前做,否则 2018 年年底的数据会和 2019 年初连成一个区间。
- 按状态分区编号是关键:不分区的话,不同状态交替时差值不再稳定。