按季度聚合需先提取年份和季度并组合为'yyyy-qn'格式再分组,不同数据库语法各异;环比计算应使用lag窗口函数,并注意nullif防除零和浮点转换;where条件须下推至日期字段以避免全表扫描。

用 DATEPART 或 EXTRACT 提取季度再 GROUP BY
SQL 本身不直接支持“按季度聚合”,必须先从日期字段中提取年份和季度,再组合成可分组的维度。不同数据库语法差异大:DATEPART(quarter, order_date) 是 SQL Server 和 PostgreSQL(需 EXTRACT(QUARTER FROM order_date))的写法;MySQL 用 QUARTER(order_date);SQLite 则得靠 strftime('%m', order_date) 手动映射到 1–4。
关键不是只取季度号,否则跨年数据会混在一起。必须同时提取年份,拼成类似 '2023-Q1' 的字符串:
SELECT CONCAT(YEAR(order_date), '-Q', DATEPART(quarter, order_date)) AS quarter_label, COUNT(*) AS cnt FROM orders GROUP BY YEAR(order_date), DATEPART(quarter, order_date) ORDER BY quarter_label;
注意:SQL Server 中 CONCAT 安全处理 NULL,而 + 拼接遇到 NULL 会整行变 NULL —— 这是线上统计突然少数据的常见原因。
计算环比增长要用窗口函数 LAG
单纯分组出每季度数值还不够,趋势需要“本季度值 / 上季度值 − 1”。不能靠子查询自连接,既慢又难对齐跨年边界(比如 2023-Q4 → 2024-Q1)。LAG() 是唯一可靠解法:
SELECT
quarter_label,
cnt,
ROUND(
(cnt * 1.0 / NULLIF(LAG(cnt) OVER (ORDER BY quarter_label), 0)) - 1,
3
) AS qoq_growth
FROM (
SELECT
CONCAT(YEAR(order_date), '-Q', DATEPART(quarter, order_date)) AS quarter_label,
COUNT(*) AS cnt
FROM orders
GROUP BY YEAR(order_date), DATEPART(quarter, order_date)
) t
ORDER BY quarter_label;
这里两个细节容易错:NULLIF(..., 0) 防除零;cnt * 1.0 强制转浮点,避免整数除法截断(如 SQL Server 中 5/3 = 1)。
WHERE 条件必须放在聚合前,别在外部过滤季度标签
有人习惯先生成 quarter_label,再用 WHERE quarter_label >= '2022-Q1' 筛——这会导致无法使用索引,全表扫描。正确做法是把时间范围下推到最内层:
- 用
order_date >= '2022-01-01' AND order_date 过滤原始行 - 避免在
CONCAT(...)结果上做字符串比较(如LIKE '202[2-4]%'),极慢且无法走索引 - 如果表有分区(如按月分区),下推日期条件还能触发分区裁剪
前端展示时注意季度顺序不是字典序
'2023-Q10' 会排在 '2023-Q2' 前面——字符串排序崩了。解决方案只有两个:
要么在 SQL 层用 ORDER BY YEAR(order_date), DATEPART(quarter, order_date) 显式排序(推荐);要么让应用层解析 quarter_label 后按年+季数值排序。别指望数据库自动懂“Q10”是第四季度。
跨年趋势里,Q4 到 Q1 的跃变最易被误读为异常波动,实际只是季节性断点——这点得靠业务同学一起看,SQL 只负责把数字算准。











