SQL Server中LAST_VALUE默认只返回当前行值,因其窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW;需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并确保ORDER BY稳定(如加id二级排序)才能正确获取组内末值。

FIRST_VALUE / LAST_VALUE 在 SQL Server 中默认不 work,必须显式写全窗口帧,否则 LAST_VALUE 就等于当前行值。
SQL Server 的 LAST_VALUE() 为什么总返回当前行?
因为它的默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是整组。哪怕你写了 PARTITION BY user_id,LAST_VALUE(amount) 也只看“从分组开头到当前行”这段里的最后一个值——当前行自然就是“最后”。这跟直觉完全相反。
- 必须强制指定完整帧:
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -
ORDER BY必须存在且稳定:时间字段重复时,一定要加唯一列(如id)做二级排序,否则结果不可复现 - SQL Server 2012+ 要求
LAST_VALUE显式声明帧,缺了直接报错;旧版本行为更不可靠
想取“每组第一条完整记录”,别只靠 FIRST_VALUE()
FIRST_VALUE() 只返回字段值,不减少行数。它会把“首值”复制到所有同组行上,不能当筛选条件用。你真正要的,通常是“每个用户只留一条最早订单的完整记录”。
- 正确做法是组合
ROW_NUMBER():ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time, id) AS rn - 外层
WHERE rn = 1才能拿到那条完整记录 - 如果只是需要首尾字段值(比如首单金额、末单金额),再加
FIRST_VALUE(amount)和LAST_VALUE(amount)作为辅助列,但别指望靠它过滤
聚合函数 MIN()/MAX() vs 窗口函数 FIRST_VALUE()/LAST_VALUE()
如果你只关心“最早时间”或“最高金额”,MIN(created_at) 和 MAX(amount) 更简洁、性能更好、兼容性更强。但注意:它们返回的是极值,不是对应那条记录的其他字段(比如最早时间对应的 status 或 user_name)。
- 要极值 → 用
MIN()/MAX(),配合GROUP BY - 要“首/尾那条记录的全部字段” → 用
ROW_NUMBER()+WHERE rn = 1 - 要“每行都标出本组首尾值”(比如做差值分析)→ 才用
FIRST_VALUE()/LAST_VALUE(),且必须带完整帧和稳定ORDER BY
最容易被忽略的一点:NULL 值在 ORDER BY 中的位置影响“第一”和“最后”的判定。SQL Server 默认 NULLS LAST,但若你在查询里没显式写 NULLS LAST 或 NULLS FIRST,而源数据又有 NULL 时间,就可能让 FIRST_VALUE() 拿到 NULL 而不是你预期的最早非空时间。

















