同比是与去年同期比较(如2024年3月 vs 2023年3月),环比是与上一周期比较(如2024年3月 vs 2024年2月);sql中需手动构造时间基准,用lag()实现稳健环比,用date_sub()自连接实现安全同比,并须处理null、除零及数据完整性问题。

什么是同比和环比,SQL里怎么定义时间维度
同比是和去年同期比(比如2024年3月 vs 2023年3月),环比是和上一周期比(比如2024年3月 vs 2024年2月)。SQL里没法直接“自动识别”同比/环比,必须靠时间字段手动构造比较基准——关键不是函数有多高级,而是你的时间粒度是否对齐。
- 时间字段必须是
DATE或能转成标准日期的类型(STRING要先用TO_DATE()或STR_TO_DATE()转) - 按月同比,得确保分组字段包含年份+月份(如
YEAR(dt)和MONTH(dt),或用DATE_FORMAT(dt, '%Y-%m')) - 按日环比,不能只用
DATE_SUB(dt, INTERVAL 1 DAY)就完事——如果某天没数据,左连接会漏掉,得用生成日期序列或窗口函数补空
Lag() 窗口函数实现环比最稳当
LAG() 是计算环比的首选,它不依赖数据是否连续,也不需要自连接,性能好、逻辑清晰。
- 写法核心:按时间排序后,取前一行的值
SELECT dt, sales, LAG(sales) OVER (ORDER BY dt) AS prev_sales FROM t_sales
- 多维度分组时,务必加
PARTITION BY,否则跨组拉数据(比如不同城市混在一起算)LAG(sales) OVER (PARTITION BY city ORDER BY dt)
- 注意 NULL:首条记录的
LAG()返回 NULL,除法前要COALESCE(prev_sales, 0)或用CASE WHEN过滤 - 别用
LEAD()反向写——容易把“本月 vs 上月”写成“上月 vs 本月”,符号搞反
同比需要 JOIN 或子查询,注意年份偏移陷阱
同比本质是“当前行”和“一年前同一周期”的匹配,最常用的是自连接 + 时间偏移。
- 安全写法:用
DATE_SUB(dt, INTERVAL 1 YEAR)构造去年日期,再关联FROM t a LEFT JOIN t b ON a.city = b.city AND DATE_SUB(a.dt, INTERVAL 1 YEAR) = b.dt
- 错误高发点:用
YEAR(dt)-1和MONTH(dt)拼接日期,遇到闰年2月29日会出错(2024-02-29 → 2023-02-29 不存在) - 如果表里没有每日全量数据(比如只有有销量的日子),建议先用 CTE 或临时表生成完整时间+维度组合,再 LEFT JOIN,否则同比值会大面积为 NULL
- PostgreSQL/Oracle 支持
LAG(..., 12)按月偏移,但 MySQL 不支持跨行步长,别硬套
计算增长率时,分母为零和 NULL 必须显式处理
同比/环比增长率公式是 (current - previous) / previous,但生产环境里 previous 为 0 或 NULL 会直接导致结果异常或报错。
- 避免写成
(sales - LAG(sales)) / LAG(sales)—— 分母是 NULL 时整条结果变 NULL,且除零不报错但返回NULL或INF(取决于数据库) - 正确姿势:
- 用
CASE WHEN prev_sales IS NULL OR prev_sales = 0 THEN NULL ELSE (sales - prev_sales) / prev_sales END - 想显示“-”或“N/A”,就用
COALESCE(ROUND(..., 4), 'N/A'),但注意类型转换(字符串不能参与后续计算) - 百分比显示统一乘 100 并
ROUND(x, 2),避免小数位过多干扰阅读
- 用
实际跑起来才发现,最难的不是写对那几行 SQL,而是确认你的原始数据里有没有缺失日期、有没有重复主键、以及业务上“同比”到底认自然年还是财年——这些不会报语法错误,但会让结果差 30%。











