子查询中通过关联同维度下时间字段严格小于当前值的最大值来获取“上期值”,如WHERE stat_month
子查询里怎么拿到“上期值”做环比计算
环比增长率本质是
(本期值 - 上期值) / 上期值,难点在于 SQL 没有天然的“上一行”概念。不能靠LAG()就完事——如果数据存在日期空缺、分组不连续(比如按产品+月份聚合后缺失某月),窗口函数可能跨过真实业务周期。这时候必须用子查询显式关联“上一个有效周期”。核心思路:对每条记录,用子查询在同维度(如
product_id)下找时间字段(如stat_month)严格小于当前值的最大值对应的数据。
- 假设主表叫
sales_summary,含字段product_id、stat_month(格式 '2024-01')、amount- 子查询要加
WHERE stat_month + <code>ORDER BY stat_month DESC LIMIT 1,确保只取紧邻上期- 必须用
LEFT JOIN或相关子查询,避免丢失首期数据(无上期时结果为 NULL,可后续用COALESCE处理)错位关联为什么不能直接用 INNER JOIN
有人会写
FROM sales_summary t1 INNER JOIN sales_summary t2 ON t1.product_id = t2.product_id AND t2.stat_month = DATE_SUB(t1.stat_month, INTERVAL 1 MONTH),这看似简洁,但实际埋雷:
DATE_SUB要求stat_month是合法日期类型;若存为字符串(如 '2024-01'),需先转成日期再减,否则可能得到 '2024-00' 这种非法值- 如果某产品在 2024-01 有数据、2024-02 缺失、2024-03 又有,这种写法会让 2024-03 关联不到 2024-01(因为硬匹配“上一月”,而非“上一个存在数据的月份”)
- 分区表或大表上,
stat_month = ...条件能走索引,但DATE_SUB(stat_month, ...)会导致索引失效子查询实现环比的典型写法(MySQL/PostgreSQL 兼容)
以下写法绕开日期运算,只依赖字段比较和排序,稳定性更高:
SELECT t1.product_id, t1.stat_month, t1.amount AS curr_amount, (SELECT t2.amount FROM sales_summary t2 WHERE t2.product_id = t1.product_id AND t2.stat_month <p>注意点:</p>
NULLIF(prev_amount, 0)防止除零;COALESCE(prev_amount, 0)让首期环比显示为 -1.0000(也可改为NULL)- PostgreSQL 中
LIMIT 1可换为FETCH FIRST 1 ROW ONLY,语义更清晰- MySQL 8.0+ 支持
LATERAL,可把子查询提到FROM子句提升可读性,但逻辑不变性能卡点在哪?怎么加索引才管用
这个子查询最慢的地方不是计算,而是每次都要扫描
sales_summary找上期——没索引时是 O(n²)。
- 必须建联合索引:
CREATE INDEX idx_prod_month ON sales_summary (product_id, stat_month);stat_month必须是字符串有序格式(如 '2024-01' > '2023-12'),若用 '202401' 数字型也行,但别用 'Jan-2024' 这类字典序混乱的- 如果数据量极大(千万级),子查询仍慢,就得考虑预计算:用每日调度任务把上期值写入宽表,或改用物化视图(PostgreSQL)/ 实时物化表(StarRocks)
真正难的不是写出这个子查询,而是想清楚“上期”在你业务里到底指什么——是日历上上个月,还是数据生产链路上最近一次产出?后者才需要错位关联,前者用窗口函数反而更稳。











