LAG()和LEAD()用于计算项目阶段持续时间时,需按project_id分组、start_time升序排序;LAG()获取上一阶段开始时间适用于间隔计算,LEAD()获取下一阶段开始时间更适合作为当前阶段结束时间,配合COALESCE处理末尾NULL,并须前置校验时间重叠、缺失等异常。

用 LAG() 获取上一阶段时间戳
项目阶段持续时间本质是「当前阶段开始时间减去上一阶段开始时间」,但 SQL 里没有天然的“上一行”概念,得靠窗口函数定位。最直接的方式是用 LAG() 拿到按项目 ID 和时间排序后的前一条记录的开始时间。
注意排序必须严格:先按 project_id 分组,再按 start_time 升序(不能只按阶段名称排,阶段名可能重复或乱序)。如果存在同一项目内阶段时间重叠或倒置,LAG() 仍会机械取前一行,结果就不可信——得先清洗数据。
-
LAG(start_time) OVER (PARTITION BY project_id ORDER BY start_time)是标准写法,别漏掉PARTITION BY,否则跨项目混算 - 如果阶段表里只有
phase和start_time,没明确的顺序字段,仅靠phase字符串排序(如 “Planning”, “Execution”)极易出错,不推荐 -
LAG()默认返回NULL(首行无前驱),计算持续时间时需用COALESCE()或CASE处理,否则整列变NULL
用 LEAD() 算阶段结束时间更稳妥
很多项目阶段表并不存 end_time,而是靠“下一阶段的 start_time”隐式定义当前阶段终点。这时用 LEAD(start_time) 比 LAG() 更符合业务逻辑——它直接给出下一阶段起点,即当前阶段自然结束时刻。
典型错误是把 LEAD() 和 LAG() 混用或顺序写反。比如想算「Execution」阶段时长,却用 LAG() 去抓「Planning」的开始时间,再减当前 start_time,这等于算的是阶段间隔而非持续时间。
- 正确姿势:
LEAD(start_time) OVER (PARTITION BY project_id ORDER BY start_time) AS next_start,然后next_start - start_time - 最后一阶段没有下一阶段,
LEAD()返回NULL,可配合COALESCE(next_start, CURRENT_TIMESTAMP)补默认值(视业务而定) - PostgreSQL 和 BigQuery 支持直接对
TIMESTAMP做减法得 interval;MySQL 需用TIMESTAMPDIFF()函数,单位要显式指定(如SECOND,DAY)
处理阶段缺失、时间重叠与多版本并行
真实项目数据常有缺口:某阶段记录丢失、两个阶段 start_time 完全相同、甚至同一时间多个阶段并行启动。窗口函数本身不校验业务合理性,只按排序机械取值,这些情况会导致持续时间为负、零或远超预期。
不能只靠窗口函数“算出来就完事”。必须前置加校验逻辑,否则报表数字好看但完全失真。
- 加
CASE WHEN next_start 过滤负值和零值 - 用
ROW_NUMBER() OVER (PARTITION BY project_id, start_time ORDER BY phase)查重——同一时间点出现多条记录,说明需人工确认是否为并发阶段 - 若阶段有明确生命周期(如 “Closed” 状态),优先用状态字段过滤有效阶段,而不是无条件信任时间戳
MySQL 8.0+ 与 PostgreSQL 的语法差异点
核心逻辑一致,但细节上容易栽跟头。比如 MySQL 不支持直接 TIMESTAMP - TIMESTAMP 得秒数,必须用 TIMESTAMPDIFF(SECOND, start_time, next_start);PostgreSQL 则允许 next_start - start_time 返回 interval 类型,再用 EXTRACT(EPOCH FROM ...) 转秒数。
另一个坑是空值传播:MySQL 的 TIMESTAMPDIFF() 遇到任一参数为 NULL 直接返回 NULL;PostgreSQL 的减法运算也遵循同样规则,但新手常误以为会跳过空值继续算。
- MySQL 示例:
TIMESTAMPDIFF(SECOND, start_time, LEAD(start_time) OVER (PARTITION BY project_id ORDER BY start_time)) - PostgreSQL 示例:
EXTRACT(EPOCH FROM (LEAD(start_time) OVER (PARTITION BY project_id ORDER BY start_time) - start_time)) - 所有数据库都需注意:
ORDER BY子句中若含NULL,不同引擎默认排序方向不同(MySQL 默认NULLS LAST,PostgreSQL 默认NULLS FIRST),显式写NULLS LAST更安全
实际跑起来之后,最容易被忽略的是阶段定义本身的歧义——比如“Design”阶段到底是从需求确认完成算起,还是原型评审通过才算?窗口函数再准,也救不了源头定义模糊的数据。

















