字节跳动大数据面试SQL-每月销售额与上月的同比环比
一、题目背景
这道题来自字节跳动电商业务的数据分析岗面试。环比和同比是数据分析中最基础的指标,但 SQL 面试中很多人因为 LAG 函数用不熟而翻车。
业务场景:月度经营分析会上,GMV 报表必须包含"环比增长"和"同比增长"两列。环比看短期趋势(这个月比上个月好还是差),同比消除季节因素(今年1月跟去年1月比)。
二、题目
现有一张月度销售汇总表 t15_zj_monthly_sales。请计算每个月的环比增长率和同比增长率。
t15_zj_monthly_sales 表
+----------+--------+
| month | sales |
+----------+--------+
| 2024-01 | 10000 |
| 2024-02 | 12000 |
| 2024-03 | 11000 |
| 2024-04 | 13000 |
| 2024-05 | 14000 |
| 2024-06 | 15000 |
| 2024-07 | 16000 |
| 2024-08 | 15500 |
| 2024-09 | 14500 |
| 2024-10 | 15000 |
| 2024-11 | 17000 |
| 2024-12 | 20000 |
| 2025-01 | 15000 |
| 2025-02 | 18000 |
| 2025-03 | 16000 |
+----------+--------+
定义:
- 环比 = (本月 - 上月) / 上月 × 100%
- 同比 = (本月 - 去年同月) / 去年同月 × 100%
期望输出:每个月的销售额、环比增长率(%)、同比增长率(%)。
三、思路分析
- 环比:
LAG(sales, 1)取上一行(上个月),适用于时间序列有序数据 - 同比:
LAG(sales, 12)取 12 行前(去年同月),前提是数据按月连续排列 - 如果数据有缺失月份,同比用 LEFT JOIN 更安全
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐️⭐️ |
| 题目清晰度 | ⭐️⭐️⭐️⭐️⭐️ |
| 业务常见度 | ⭐️⭐️⭐️⭐️⭐️ |
四、逐步推导
步骤1:环比
Spark SQL
SELECT
month,
sales,
LAG(sales, 1) OVER (ORDER BY month) AS prev_month_sales,
ROUND((sales - LAG(sales, 1) OVER (ORDER BY month)) * 100.0
/ LAG(sales, 1) OVER (ORDER BY month), 1) AS mom_pct
FROM t15_zj_monthly_sales
ORDER BY month;
+----------+--------+-------------------+----------+
| month | sales | prev_month_sales | mom_pct |
+----------+--------+-------------------+----------+
| 2024-01 | 10000 | NULL | NULL |
| 2024-02 | 12000 | 10000 | 20.0 |
| 2024-03 | 11000 | 12000 | -8.3 |
| 2024-04 | 13000 | 11000 | 18.2 |
| 2024-05 | 14000 | 13000 | 7.7 |
| 2024-06 | 15000 | 14000 | 7.1 |
| 2024-07 | 16000 | 15000 | 6.7 |
| 2024-08 | 15500 | 16000 | -3.1 |
| 2024-09 | 14500 | 15500 | -6.5 |
| 2024-10 | 15000 | 14500 | 3.4 |
| 2024-11 | 17000 | 15000 | 13.3 |
| 2024-12 | 20000 | 17000 | 17.6 |
| 2025-01 | 15000 | 20000 | -25.0 |
| 2025-02 | 18000 | 15000 | 20.0 |
| 2025-03 | 16000 | 18000 | -11.1 |
+----------+--------+-------------------+----------+
15 rows selected (0.905 seconds)(https://www.dwsql.com)
第一行没有上月数据,环比为 NULL。
步骤2:同比
Spark SQL
SELECT
month,
sales,
LAG(sales, 12) OVER (ORDER BY month) AS last_year_sales,
ROUND((sales - LAG(sales, 12) OVER (ORDER BY month)) * 100.0
/ LAG(sales, 12) OVER (ORDER BY month), 1) AS yoy_pct
FROM t15_zj_monthly_sales
ORDER BY month;
执行结果
+----------+--------+------------------+----------+
| month | sales | last_year_sales | yoy_pct |
+----------+--------+------------------+----------+
| 2024-01 | 10000 | NULL | NULL |
| 2024-02 | 12000 | NULL | NULL |
| 2024-03 | 11000 | NULL | NULL |
| 2024-04 | 13000 | NULL | NULL |
| 2024-05 | 14000 | NULL | NULL |
| 2024-06 | 15000 | NULL | NULL |
| 2024-07 | 16000 | NULL | NULL |
| 2024-08 | 15500 | NULL | NULL |
| 2024-09 | 14500 | NULL | NULL |
| 2024-10 | 15000 | NULL | NULL |
| 2024-11 | 17000 | NULL | NULL |
| 2024-12 | 20000 | NULL | NULL |
| 2025-01 | 15000 | 10000 | 50.0 |
| 2025-02 | 18000 | 12000 | 50.0 |
| 2025-03 | 16000 | 11000 | 45.5 |
+----------+--------+------------------+----------+
15 rows selected (0.267 seconds)(https://www.dwsql.com)
24年数据为空,因为24年没有上一年的数据
步骤3:合并环比和同比
Spark SQL
SELECT
month,
sales,
ROUND((sales - LAG(sales, 1) OVER (ORDER BY month)) * 100.0
/ LAG(sales, 1) OVER (ORDER BY month), 1) AS mom_pct,
ROUND((sales - LAG(sales, 12) OVER (ORDER BY month)) * 100.0
/ LAG(sales, 12) OVER (ORDER BY month), 1) AS yoy_pct
FROM t15_zj_monthly_sales
ORDER BY month;
最终结果(完整数据25行,截取关键月份):
+----------+--------+----------+----------+
| month | sales | mom_pct | yoy_pct |
+----------+--------+----------+----------+
| 2024-01 | 10000 | NULL | NULL |
| 2024-02 | 12000 | 20.0 | NULL |
| 2024-03 | 11000 | -8.3 | NULL |
| 2024-04 | 13000 | 18.2 | NULL |
| 2024-05 | 14000 | 7.7 | NULL |
| 2024-06 | 15000 | 7.1 | NULL |
| 2024-07 | 16000 | 6.7 | NULL |
| 2024-08 | 15500 | -3.1 | NULL |
| 2024-09 | 14500 | -6.5 | NULL |
| 2024-10 | 15000 | 3.4 | NULL |
| 2024-11 | 17000 | 13.3 | NULL |
| 2024-12 | 20000 | 17.6 | NULL |
| 2025-01 | 15000 | -25.0 | 50.0 |
| 2025-02 | 18000 | 20.0 | 50.0 |
| 2025-03 | 16000 | -11.1 | 45.5 |
+----------+--------+----------+----------+
15 rows selected (0.325 seconds)(https://www.dwsql.com)
2025-01 的同比增长 50%(15000 vs 10000),增长显著。
五、常见坑点
坑1:LAG 依赖排序
如果表不按时间排序,LAG 取到的是随机行。必须 ORDER BY month。
坑2:缺失月份导致同比错位
如果 2024-07 数据缺失,LAG(sales, 12) 对 2025-07 取到的是 2024-06 而非 2024-07,同比完全错误!数据有缺失时必须用 LEFT JOIN 方案替代 LAG。
坑3:分母为零
如果上个月销售额为 0,除法会报错。加 NULLIF(LAG(sales) OVER w, 0) 兜底。
六、举一反三
- 累计同比:
SUM(sales) OVER (ORDER BY month ROWS BETWEEN 11 PRECEDING AND CURRENT ROW)计算滚动12个月的累计销售额 - 移动平均:
AVG(sales) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)3个月移动平均,平滑波动
七、知识点总结
| 考点 | 说明 |
|---|---|
| LAG(sales, 1) | 取上一行 = 环比 |
| LAG(sales, 12) | 取12行前 = 同比(连续数据) |
| 数据缺失 | 用 LEFT JOIN 替代 LAG 保证准确性 |
八、建表语句和数据插入
点击展开 DDL & DML
CREATE TABLE IF NOT EXISTS t15_zj_monthly_sales (
month STRING, sales BIGINT
);
INSERT INTO t15_zj_monthly_sales VALUES
('2024-01',10000),('2024-02',12000),('2024-03',11000),('2024-04',13000),
('2024-05',14000),('2024-06',15000),('2024-07',16000),('2024-08',15500),
('2024-09',14500),('2024-10',15000),('2024-11',17000),('2024-12',20000),
('2025-01',15000),('2025-02',18000),('2025-03',16000);
📱关注公众号
「数据仓库技术」文章同步更新,不错过每一篇干货

💬加群交流
备注「数据仓库技术」加入社群,每日一道大厂SQL真题
