SQL中计算同比和环比的核心是:先按时间分组聚合,再用LAG()拉取相邻周期值并运算;关键在时间对齐、分区清晰、空值处理。
SQL 中用窗口函数计算同比(Year-on-Year)和环比(Month-on-Month)的核心是:先按时间分组聚合,再用 LAG() 或 LEAD() 拉取相邻周期的值,最后做差值或比值运算。关键在于时间对齐、分区逻辑清晰、空值处理得当。
原始明细表(如订单表)通常按天记录,需先按年月(或年份)汇总,生成时间序列基准表:
GROUP BY YEAR(order_date), MONTH(order_date) 或 DATE_FORMAT(order_date, '%Y-%m')(MySQL)/ TO_CHAR(order_date, 'YYYY-MM')(PostgreSQL)统一时间粒度SUM(amount)、COUNT(*),结果作为后续窗口计算的输入ym = DATE_TRUNC('month', order_date)),避免字符串排序错乱使用 LAG(value, 1) OVER (ORDER BY ym) 获取前一个自然月的值:
LAG(sales, 1) OVER (ORDER BY ym) 返回同列上一行(即上月)的销售额sales - LAG(sales);环比增长率 = ROUND((sales - LAG(sales)) * 100.0 / NULLIF(LAG(sales), 0), 2)
NULLIF(LAG(sales), 0) 防止除零;首行 LAG 返回 NULL,对应环比值也为 NULL,符合业务实际关键在正确“跨年对齐”,推荐两种方式:
year, month 排序,再用 LAG(sales, 12) OVER (ORDER BY year, month) —— 假设数据按月连续且无缺失,第13行就是去年同月LAG(sales) OVER (ORDER BY ym) FILTER (WHERE ym = ym - INTERVAL '1 year');但多数引擎不支持 FILTER,此时更适合用子查询或 CTE 先构造“去年同月”字段,再 LEFT JOINCOALESCE 或保留 NULL 体现
实际写 SQL 时容易忽略这些细节:
LAG 行为不确定)WHERE 中提前过滤掉某个月份——否则 LAG 会跳过空缺,导致同比错位;应在聚合后过滤或用 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 控制窗口范围SUM() OVER (ORDER BY ym ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) 先算滚动和,再套 LAGADD_MONTHS,Snowflake 用 DATEADD('month', -1, ym),写法需适配