当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 用 LAG 比较相邻记录时怎样处理首行

MySQL 用 LAG 比较相邻记录时怎样处理首行

来源:17golang原创 2026-10-17 22:05:34 0浏览 收藏

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。因此首行不是整张结果的唯一一行,而是每个窗口分区各有一行。

MySQL LAG 按窗口分区和业务日期比较相邻记录的静态结构图
图1:静态说明图展示分组边界、当前行与上一条记录的对应关系;它不是数据库截图,也不表示执行时序。

默认写法为什么会在首行返回 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() 可能不是同一条“上一记录”。

MySQL LAG 区分真实 NULL、无上一行和业务默认值的静态结构图
图2:静态结构图对比保留 NULL、使用 default 0 和输出首行标记三种业务解释;它不是查询结果截图。

偏移量大于 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 并增加首行标记。

只按时间排序会有什么问题?

同一分区中时间相同的记录没有唯一顺序,上一条关系可能不稳定。追加唯一键或其他确定性字段,才能让比较结果可复现。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL 窗口函数取每组最新记录时怎样稳定排序MySQL 窗口函数取每组最新记录时怎样稳定排序
上一篇
MySQL 窗口函数取每组最新记录时怎样稳定排序
MySQL JSON_TABLE 展开数组后怎样避免重复行
下一篇
MySQL JSON_TABLE 展开数组后怎样避免重复行
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    507次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    569次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    593次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    546次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    364次使用