MySQL 用 LAG 比较相邻记录时怎样处理首行
来源:17golang原创
时间:2026-10-17 22:05:34 303浏览 收藏
MySQL 用 LAG 比较相邻记录时怎样处理首行
MySQL 的 LAG() 比较相邻记录时,分组中的第一行没有上一行,所以默认结果是 NULL。如果业务需要把首行当作 0、某个标签或“无法比较”,应在第三个参数中显式传入 default;如果还要区分“首行缺少上一条”和“上一条真实值就是 NULL”,就同时输出行号或首行标记。关键是先固定 PARTITION BY 和稳定的 ORDER BY,再决定缺失值语义。
官方文档:https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html。本文只讨论 LAG() 的偏移、首行和默认值,不展开窗口帧优化。
先把“相邻”定义成分组内的稳定顺序
LAG(expr, 1) 不是按表的物理存储顺序找上一条记录,而是按窗口中的排序结果找偏移行。多租户、账户或商品历史通常需要先按业务键分组,再用时间和唯一键组成排序条件。只按时间排序时,如果同一时间有多条记录,哪一条算“上一条”可能不稳定。
-- 每个账户独立比较;event_id 用来打破相同时间的并列顺序。
SELECT
account_id,
event_id,
event_time,
amount,
LAG(amount) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS previous_amount
FROM account_events;
这里第一条记录的 previous_amount 是 NULL,分组切换后新分组的第一条也会重新得到 NULL。因此首行不是整张结果的唯一一行,而是每个窗口分区各有一行。

默认写法为什么会在首行返回 NULL
LAG() 的常用形式是 LAG(expr [, N [, default]])。不写 N 时偏移量是 1,不写 default 时缺少目标行返回 NULL。当 N 大于 1 时,分组开头会有多行没有足够的前置记录,这些行都会落入 default 规则。
-- 首行没有上一条记录,默认保留 NULL。
SELECT
account_id,
event_time,
amount,
LAG(amount, 1, NULL) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS previous_amount
FROM account_events;
要注意两个看起来相同、含义却不同的情况:一是当前行确实是分组首行,根本不存在上一行;二是上一行存在,但上一行的 amount 本身是业务上的真实 NULL。只看 previous_amount IS NULL 无法把两者区分开。
需要可计算数值时,把 default 写成业务零值
如果后续要计算“当前金额减上一笔金额”,首行使用 0 往往比传播 NULL 更方便。但这不是 LAG() 的通用正确答案:零值代表业务含义,只有在业务确认“没有上一笔”等价于 0 时才这样写。
-- 只在业务约定首行基线为 0 时使用第三个参数。
SELECT
account_id,
event_time,
amount,
LAG(amount, 1, 0) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS previous_amount,
amount - LAG(amount, 1, 0) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS amount_delta
FROM account_events;
这种写法会把首行的差值算成当前金额。它适合“从零开始累计”或“首笔没有历史值就按零计算”的报表;不适合需要把首行排除在环比、波动率或异常检测之外的场景。
用行号标记首行,避免把缺失和真实 NULL 混在一起
最稳妥的做法通常是保留 NULL,同时用同一个分区与排序规则计算 ROW_NUMBER()。这样后续可以按 row_number = 1 处理首行,也能继续保留上一条记录真实为空的事实。
-- 行号标识窗口首行;previous_amount 的 NULL 仍保留原始语义。
WITH ordered_events AS (
SELECT
account_id,
event_id,
event_time,
amount,
ROW_NUMBER() OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS row_number_in_account,
LAG(amount) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS previous_amount
FROM account_events
)
SELECT
account_id,
event_id,
event_time,
amount,
previous_amount,
row_number_in_account = 1 AS is_first_row
FROM ordered_events;
如果数据库或团队约定不使用布尔表达式,也可以把最后一列写成 CASE WHEN row_number_in_account = 1 THEN 1 ELSE 0 END。注意两次窗口定义必须保持一致,否则行号标记和 LAG() 可能不是同一条“上一记录”。

偏移量大于 1 时,首部会连续出现缺失值
当查询要比较前两条或前三条记录时,缺失范围不再只有第一行。例如 LAG(amount, 2) 在每个分组的前两行都会没有对应记录;如果填入 default 0,前两行都会得到 0。不要把这个 default 误认为真实的历史金额。
-- 偏移 2 会让每个分区的前两行进入 default 规则。
SELECT
account_id,
event_time,
amount,
LAG(amount, 2, NULL) OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS amount_two_rows_ago,
ROW_NUMBER() OVER (
PARTITION BY account_id
ORDER BY event_time, event_id
) AS row_number_in_account
FROM account_events;
如果偏移量来自参数,仍要确认它是非负整数,并结合分组长度决定缺失值策略。偏移 0 表示读取当前行,不是“没有上一行”。
按现象检查 LAG 的首行问题
| 现象 | 优先检查 | 合适的处理 |
|---|---|---|
| 每个账户第一条都是 NULL | 是否刚好是分区首行 | 保留 NULL,或显式设置业务 default |
| 同一时间的上一条不稳定 | ORDER BY 是否缺少唯一键 | 追加 event_id 等稳定排序键 |
| 首行被当作真实缺失金额 | 是否只判断 previous_amount IS NULL | 增加 ROW_NUMBER 首行标记 |
| 前两行都出现默认值 | LAG 的 N 是否为 2 或更大 | 按偏移量理解缺失范围 |
| 跨账户比较到了上一账户 | 是否漏写 PARTITION BY | 把业务分组键写进窗口定义 |
结论:先保留语义,再决定是否填充值
处理 MySQL LAG() 首行时,可以按这个顺序做决定:先用稳定排序定义相邻关系;默认让无上一行返回 NULL;需要计算时才使用第三个参数填入业务默认值;需要审计或区分真实空值时,再输出 ROW_NUMBER() 或首行标记。这样既不会把首行误认为上一条记录为空,也不会因为方便计算而悄悄改变报表含义。
LAG 的首行为什么是 NULL?
因为分区首行不存在偏移 1 的前置记录;不提供 default 时,MySQL 按规则返回 NULL。
能不能用 COALESCE 代替 LAG 的 default?
可以写成 COALESCE(LAG(amount) OVER (...), 0),但它也会把上一行真实为 NULL 的情况改成 0。需要区分两种情况时,优先保留 NULL 并增加首行标记。
只按时间排序会有什么问题?
同一分区中时间相同的记录没有唯一顺序,上一条关系可能不稳定。追加唯一键或其他确定性字段,才能让比较结果可复现。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习