同比计算需用date_format(date, '%y-%m')按年月粒度匹配同期数据,避免日级错配;若需精确到日,须确保历史数据完整,否则子查询返回null,并务必加where限制范围以防性能暴跌。

子查询里怎么写同比(year-on-year)计算
同比本质是「当前期数值 ÷ 同期去年数值 − 1」,关键难点在于子查询如何精准拉出去年同一周期的数据。不能简单用 DATE_SUB(date, INTERVAL 1 YEAR) 然后 JOIN,因为日期可能不完全对齐(比如节假日错位、月末/季末天数不同),更稳妥的方式是用相关子查询按业务周期匹配。
常见错误是直接在 WHERE 里写 YEAR(date) = YEAR(NOW()) - 1 AND MONTH(date) = MONTH(NOW()) —— 这会漏掉跨年但实际属于同一自然月的记录(如 2024-01-31 和 2023-01-31 都存在,但 2023-02-31 不存在,导致 2024-02 的子查询返回空)。
- 推荐用
DATE_FORMAT(date, '%Y-%m')统一到年月粒度,在子查询中按该字段关联,避免日级错配 - 若需精确到日且保证存在性,用
(SELECT value FROM t WHERE DATE_FORMAT(date, '%Y-%m-%d') = DATE_FORMAT(DATE_SUB(outer.date, INTERVAL 1 YEAR), '%Y-%m-%d')),但必须确保历史数据完整,否则结果为NULL - 务必给子查询加
WHERE条件限制范围(如AND date BETWEEN '2023-01-01' AND '2023-12-31'),否则性能暴跌
环比(month-on-month)为什么不能只用LAG()替代子查询
虽然 LAG() 窗口函数更简洁高效,但子查询仍是必要场景:当主表和历史表结构不同、或需聚合后对比(如月均值 vs 上月均值)、或数据库版本不支持窗口函数(如 MySQL
典型陷阱是子查询返回多行 —— 比如没加 LIMIT 1 或没用聚合函数,导致报错 Subquery returns more than 1 row。
- 写环比子查询时,外层每行必须对应唯一一行内层结果,用
MAX(date)或ORDER BY date DESC LIMIT 1确保单值 - 如果环比单位是「上月同期」(如 2024-03-15 对比 2024-02-15),用
DATE_SUB(outer.date, INTERVAL 1 MONTH);如果是「上一个自然月」(2024-03 全月 vs 2024-02 全月),则用DATE_FORMAT(DATE_SUB(outer.date, INTERVAL 1 MONTH), '%Y-%m')关联 - 注意时区:若
date是TIMESTAMP类型,DATE_SUB受会话时区影响,建议统一转为DATE再运算
子查询嵌套层级深了,性能卡在哪
三层及以上子查询(比如同比 + 环比 + 行业均值)极易触发全表扫描,尤其当子查询未走索引时。MySQL 5.7 及更早版本对相关子查询优化差,每行外层记录都会重复执行内层查询。
- 检查执行计划:用
EXPLAIN看子查询是否显示DEPENDENT SUBQUERY,如果是,说明无法提前物化,性能随主表行数线性恶化 - 强制走索引:在子查询的
WHERE条件中,确保用于关联的字段(如date、product_id)有复合索引,例如INDEX (date, category) - 能用
JOIN就别硬套子查询:把多个子查询合并成一次LEFT JOIN多张临时表(用WITH或派生表),通常快 3–5 倍
NULL 值怎么处理才不影响同比结果
子查询返回 NULL 时,value / NULL 结果仍是 NULL,但业务上常需显示为 0 或 -100%。直接用 IFNULL() 容易掩盖真实缺失问题。
- 先判断子查询是否真无数据:用
(SELECT COUNT(*) FROM ...)辅助验证,避免把数据缺失当成计算逻辑问题 - 同比公式建议写成
IFNULL((cur.value - last.value) / NULLIF(last.value, 0), 0),其中NULLIF(last.value, 0)防止除零,外层IFNULL统一兜底 - 若同比分母为 0(如去年销量为 0),数学上无定义,应单独标记为
'N/A'或NULL,而非强行算出极大值
真正麻烦的是时间维度对齐 —— 子查询看似写对了,但因业务日历(如财年从 4 月开始)或数据延迟入库,导致“同期”根本没数据。这种问题没法靠 SQL 语法解决,得查上游 ETL 调度和分区策略。










