“生命周期周期表”是按业务单号和状态段聚合出每段连续相同状态的起止时间的表,需用lag识别状态变化、sum累计求和生成segment_id、再group by聚合并用lead处理结束时间。

什么是“生命周期周期表”:先确认你要的不是简单 GROUP BY
直接用 GROUP BY 按业务单号聚合,只能拿到每个单号的静态快照(比如最新状态、最早时间),但生命周期强调「状态如何随时间演变」——比如一个订单从 'created' → 'paid' → 'shipped' → 'delivered',中间可能反复退回到 'pending_review',还可能跨天、跨系统录入时间戳。真正的生命周期表要能表达出每一段连续相同状态的起止时间,也就是「状态段(state interval)」。
用窗口函数识别状态变化边界:LAG + CASE 找出每次切换
核心思路是:对每个业务单号按时间排序,对比当前行状态和上一行状态是否不同。一旦不同,就标记为新状态段的起点。
常见错误是只用 ROW_NUMBER() 或 RANK(),它们无法感知状态是否真的变了;必须用 LAG() 抓上一行值做显式比较:
SELECT
order_id,
status,
event_time,
CASE WHEN LAG(status) OVER (PARTITION BY order_id ORDER BY event_time) = status
THEN 0 ELSE 1 END AS is_new_segment
FROM events;
这个 is_new_segment 是后续分组的关键锚点。注意:ORDER BY event_time 必须严格,如果有毫秒级重复时间,得加二级排序(如 event_id)避免非确定性结果。
用累计求和生成分组 ID:把连续相同状态打成同一段
is_new_segment 是 0/1 序列,对其做累计求和(SUM(...) OVER (...) ),就能为每个状态段分配唯一且连续的编号:
SELECT
order_id,
status,
event_time,
SUM(is_new_segment) OVER (PARTITION BY order_id ORDER BY event_time) AS segment_id
FROM (
SELECT order_id, status, event_time,
CASE WHEN LAG(status) OVER (PARTITION BY order_id ORDER BY event_time) = status
THEN 0 ELSE 1 END AS is_new_segment
FROM events
) t;
有了 segment_id,就可以放心 GROUP BY order_id, status, segment_id 去取每段的最小/最大时间了。别漏掉 status —— 同一单号下不同状态可能有相同 segment_id 值(比如状态跳变后又跳回来),必须联合分组。
聚合出起止时间并处理边界:MIN/MAX 不够,要防数据错位
直接 MIN(event_time) 和 MAX(event_time) 看似合理,但实际中常遇到两类问题:
- 状态事件只有“进入该状态”的时间,没有“离开该状态”的时间,所以末段的结束时间需补为
NEXT_SEGMENT_START_TIME - INTERVAL '1 second'或业务定义的默认值(如CURRENT_TIMESTAMP) - 存在脏数据:同一单号同状态的多条记录时间乱序、或时间戳为 NULL,会导致
MIN/MAX错误;建议前置清洗,或用IGNORE NULLS(PostgreSQL/Oracle 支持)、或子查询过滤
典型终态 SQL 片段(以 PostgreSQL 为例):
SELECT
order_id,
status,
MIN(event_time) AS valid_from,
COALESCE(
LEAD(MIN(event_time)) OVER (PARTITION BY order_id ORDER BY MIN(event_time)) - INTERVAL '1 second',
CURRENT_TIMESTAMP
) AS valid_to
FROM segments_grouped
GROUP BY order_id, status, segment_id;
这里 LEAD(...) 取下一段开始时间再减一秒,是业界常用做法;但要注意:如果业务要求“状态生效即刻覆盖”,那就不该减秒,而是让 valid_to 和下一段 valid_from 相等(闭区间重叠),具体取决于你的下游消费逻辑。
真正难的不是写出来,而是确认时间字段来源是否可信、状态枚举是否完备、以及「状态不变但业务含义已变」(比如两次 'reviewing' 实际对应不同审核环节)这类隐性变迁——这些没法靠 SQL 自动识别,得靠业务侧对齐定义。










