如何用SQL窗口函数LAG和LEAD计算同比与环比数据

来源:Python编程网作者:泰国程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《如何用SQL窗口函数LAG和LEAD计算同比与环比数据》,敬请观看详情。同比看去年同期、环比看上一周期,这类时间维度对比在经营报表里几乎绕不开。传统写法要靠自连接把两张表按月份对齐,语句长且容易漏掉边界月。LAG与LEAD是SQL里的偏移窗口函数,能直接按指定排序取前N行或后N行的值,无需join即可在同一行拿到历史数据。以销售表为例,用LAG(amount, 1)可取上月销售额算环比,LAG(amount, 12)取去年同月算同比,再套一层表达式得出增长率。这种方式逻辑集中、执行计划更轻,适配MySQL8、PostgreSQL、Oracle等主流库。下文给出建表、数据准备与完整查询示例,并说明空值处理和性能注意点。

在业务报表开发中,同比和环比是最基础也最常用的指标计算方式。同比通常指与上年同一时期相比,环比则是与相邻的上一个统计周期相比。过去很多团队习惯用自连接或者相关子查询来对齐时间,不仅SQL冗长,而且在缺失某些月份数据时结果容易出错。窗口函数LAG和LEAD的出现,让我们可以在不破坏原表行数的前提下,直接拿到当前行之前或之后的某行数值,从而用一行表达式完成对比。

如何用SQL窗口函数LAG和LEAD计算同比与环比数据

一、LAG与LEAD函数基本用法

LAG和LEAD都属于偏移类的窗口函数。LAG用于获取当前行之前第N行的数据,LEAD用于获取当前行之后第N行的数据。它们的基本语法结构相同,都包含三个参数:第一个是取值的列,第二个是偏移量(默认是1),第三个是当偏移超出范围时返回的默认值(可省略,默认返回NULL)。

窗口函数的定义依赖于OVER子句,在OVER中通过PARTITION BY划分分组,通过ORDER BY确定偏移的顺序。例如,在按店铺分区、按月份排序的情况下,LAG(amount, 1)就是取同一个店铺上个月的数据。理解这一点非常关键,因为如果没有正确的ORDER BY,偏移方向就会混乱,计算结果毫无意义。

-- 基本语法示例
SELECT
  shop_id,
  stat_month,
  amount,
  LAG(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month) AS prev_month_amount,
  LEAD(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month) AS next_month_amount
FROM sales_monthly;

二、准备示例数据

为了直观演示,我们先建立一张简单的月度销售表,并插入跨年的数据。表中包含店铺编号、统计月份和销售额三个字段。注意月份使用标准的YYYY-MM格式字符串,这样在按字符串排序时也能自然保持时间顺序,不需要额外转换成日期类型。

下面给出的数据里,每个店铺都有连续十二个月以上的记录,方便我们观察同比(间隔12个月)和环比(间隔1个月)的对应关系。实际业务中如果某些月份缺失,LAG和LEAD会如实返回NULL,我们后续会讲如何处理这种空值。

CREATE TABLE sales_monthly (
  shop_id INT,
  stat_month VARCHAR(7),
  amount DECIMAL(12,2)
);

INSERT INTO sales_monthly VALUES
(1, '2022-01', 1000.00),
(1, '2022-02', 1200.00),
(1, '2023-01', 1500.00),
(1, '2023-02', 1700.00),
(2, '2022-01', 800.00),
(2, '2022-02', 900.00),
(2, '2023-01', 1100.00),
(2, '2023-02', 1300.00);

三、使用LAG计算环比与同比

环比只需要取前一行数据,也就是偏移量为1的LAG值。我们用当前月销售额减去上月销售额,再除以上月销售额,就得到环比增长率。对于同比,偏移量应设为12,即取上一年同月的销售额进行计算。由于我们在OVER中按shop_id分区,所以不同店铺之间不会互相错位。

在写计算表达式时,建议对分母做NULL或者零值保护,否则当某个月没有上月数据时,除法会返回NULL,影响报表展示。可以用COALESCE把NULL转换成0,或者直接在外层用CASE WHEN过滤掉无法计算的首月记录。

SELECT
  shop_id,
  stat_month,
  amount,
  -- 环比:上月数据
  LAG(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month) AS last_month,
  -- 同比:去年同月数据
  LAG(amount, 12) OVER (PARTITION BY shop_id ORDER BY stat_month) AS last_year_same_month,
  -- 环比增长率
  CASE
    WHEN LAG(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month) IS NULL THEN NULL
    ELSE (amount - LAG(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month))
         / LAG(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month)
  END AS mom_rate,
  -- 同比增长率
  CASE
    WHEN LAG(amount, 12) OVER (PARTITION BY shop_id ORDER BY stat_month) IS NULL THEN NULL
    ELSE (amount - LAG(amount, 12) OVER (PARTITION BY shop_id ORDER BY stat_month))
         / LAG(amount, 12) OVER (PARTITION BY shop_id ORDER BY stat_month)
  END AS yoy_rate
FROM sales_monthly;

四、用LEAD做反向校验与未来预估

LEAD函数和LAG方向相反,它取的是当前行之后的数据。在同比环比场景里,LEAD用得相对少,但可以用来做反向校验,比如从年末往前看,确认下一年同月是否已经入库;或者在做滚动预测时,拿当前行去对比下一周期的目标值。

下面示例展示了如何用LEAD取出下个月和明年同月的值,并计算其与当前月的差值。这种写法在制作双向对比报表时很方便,不需要再写第二个查询。

SELECT
  shop_id,
  stat_month,
  amount,
  LEAD(amount, 1) OVER (PARTITION BY shop_id ORDER BY stat_month) AS next_month,
  LEAD(amount, 12) OVER (PARTITION BY shop_id ORDER BY stat_month) AS next_year_same_month
FROM sales_monthly;

五、空值处理与性能注意点

当偏移量超出分组边界,LAG和LEAD会返回NULL。在报表中,首月没有环比、前十二个月没有同比是正常现象。如果希望用0代替NULL参与后续运算,可以在函数里传第三个参数,例如LAG(amount, 1, 0)。但要注意,用0做分母会得到除零错误,所以更稳妥的做法是保留NULL并用CASE WHEN控制输出。

从性能角度看,LAG和LEAD只需对数据做一次分区排序,执行效率远高于自连接。但在数据量极大时,依然要确保PARTITION BY和ORDER BY的列有合适索引,或者统计信息准确,避免数据库在排序阶段产生大量临时文件。另外,如果业务里月份本身可能不连续,用固定偏移量12算同比会错位,此时应先生成连续月份维度表再做关联,而不是盲目依赖偏移量。

对比方式偏移量常用函数首期数据表现
环比1LAG首月为NULL
同比12LAG前12个月为NULL
下期对比1或12LEAD末期为NULL

六、完整查询示例

最后给出一个可直接运行的完整查询,把环比和同比增长率以百分比形式展示出来,并过滤掉无法计算的首年记录。你可以把它包装成视图,供前端报表直接调用。

SELECT
  shop_id,
  stat_month,
  amount,
  ROUND(
    (amount - LAG(amount, 1) OVER w) / NULLIF(LAG(amount, 1) OVER w, 0) * 100,
    2
  ) AS mom_growth_percent,
  ROUND(
    (amount - LAG(amount, 12) OVER w) / NULLIF(LAG(amount, 12) OVER w, 0) * 100,
    2
  ) AS yoy_growth_percent
FROM sales_monthly
WINDOW w AS (PARTITION BY shop_id ORDER BY stat_month);

通过上述写法,我们用标准的SQL窗口函数清晰实现了同比和环比的计算,既减少了表关联,也降低了脚本维护成本。在大部分支持窗口函数的数据库里,这套逻辑都可以无缝迁移。

SQLLAGLEAD修改时间:2026-08-05 00:36:42

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。