因为MySQL、SQLite等数据库不支持PIVOT语法,而GROUP BY配合CASE WHEN+聚合函数(如SUM/COUNT)是兼容性最强、可控性最高的行转列方案,且能正确按分组生成多列结果。

为什么不能直接用 PIVOT?
很多数据库(比如 MySQL、SQLite、老版本 PostgreSQL)压根不支持 PIVOT 语法,而业务又常需要把行转成列——比如把“每个用户每种订单状态的数量”变成“待支付/已发货/已完成”三列。这时候就得靠子查询硬凑,但不是所有子查询写法都靠谱。
GROUP BY + 多个 CASE WHEN 子查询最稳
这不是“嵌套子查询”,而是把每个目标列写成一个带条件聚合的表达式,全部放在主 SELECT 里,配合 GROUP BY。它兼容性最好,性能也比多层子查询好得多。
- 错误写法:
SELECT (SELECT COUNT(*) FROM orders WHERE status='paid') AS paid_count—— 这会返回单行结果,无法按用户分组 - 正确写法:用
CASE WHEN status = 'paid' THEN 1 ELSE 0 END配合SUM()或COUNT() - 示例:
SELECT user_id, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped_count, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count FROM orders GROUP BY user_id;
-
SUM比COUNT更安全:如果CASE返回NULL,COUNT会忽略,SUM默认当 0 处理(除非显式写ELSE NULL)
用关联子查询也能做,但要小心性能和 NULL
当目标列逻辑复杂、没法塞进 CASE(比如要取最新一条记录的字段),才考虑用关联子查询。但它会在主表每行执行一次,数据量一大就慢。
- 必须加
WHERE关联条件,否则变成笛卡尔积:o2.user_id = o1.user_id - 子查询必须只返回 0 或 1 行,否则报错:
Subquery returns more than 1 row - 如果没匹配到,子查询结果是
NULL,不是 0 —— 要用COALESCE(..., 0)补默认值 - 示例(取每个用户最近一笔待支付订单金额):
SELECT o1.user_id, COALESCE(( SELECT amount FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.status = 'pending' ORDER BY created_at DESC LIMIT 1 ), 0) AS latest_pending_amount FROM (SELECT DISTINCT user_id FROM orders) o1;
MySQL 8.0+ 有 PIVOT?别信,那是误传
MySQL 官方至今(包括 8.0 和 9.0 dev)**没有原生 PIVOT 语法**。网上看到的所谓“MySQL PIVOT 示例”,全是用 CASE WHEN 模拟的,或者混用了窗口函数+变量 hack,不稳定也不推荐。
- PostgreSQL 从 11 开始支持
tablefunc扩展的crosstab(),但需手动安装扩展,且输入必须严格排序 - SQL Server 和 Oracle 的原生
PIVOT内部也是优化过的等价改写,不是魔法——它们底层照样生成类似CASE的计划 - 真正要注意的是:动态列(列名不确定)无法用纯 SQL 安全实现,必须靠应用层拼接或存储过程,这点不管用哪种方式都绕不开
实际写的时候,先想清楚要不要动态列、有没有 NULL 边界、数据量是否上百万——这些比纠结“子查询怎么嵌套”影响更大。










