pivot 并非标准 sql,仅 sql server 和 oracle 原生支持;mysql 完全不支持,postgresql 14+ 仅限 table 函数中使用且语法不兼容;跨数据库通用方案是手写 case when + 聚合。

PIVOT 在 MySQL 和 PostgreSQL 里根本不存在
标准 SQL 没有 PIVOT 关键字,它只是 SQL Server 和 Oracle 的扩展语法。MySQL 完全不支持 PIVOT 或 UNPIVOT;PostgreSQL 从 14 开始支持 PIVOT,但仅限于 TABLE 函数上下文,且不兼容 SQL Server 写法。
所以你看到的「跨数据库 PIVOT」本质上是伪命题——真正能跑通的只有手写 CASE WHEN + 聚合。
- SQL Server 用户迁移到其他库时,
PIVOT查询会直接报错:ERROR: syntax error at or near "PIVOT" - PostgreSQL 14+ 的
PIVOT是表函数,必须配合FROM使用,不能像 SQL Server 那样接在SELECT后面 - SQLite、MariaDB、Redshift 等主流引擎至今无原生
PIVOT支持
CASE WHEN 实现动态列聚合的关键写法
用 CASE WHEN 模拟 PIVOT 的核心逻辑是:对每个目标列值做条件判断,再用聚合函数收口。难点不在语法,而在列值是否已知、是否需要动态生成。
例如把销售记录按季度汇总成一行:
SELECT product, SUM(CASE WHEN quarter = 'Q1' THEN amount END) AS q1, SUM(CASE WHEN quarter = 'Q2' THEN amount END) AS q2, SUM(CASE WHEN quarter = 'Q3' THEN amount END) AS q3, SUM(CASE WHEN quarter = 'Q4' THEN amount END) AS q4 FROM sales GROUP BY product;
-
CASE WHEN分支里不能用变量或子查询来决定列名——列名必须静态写出 - 所有分支的返回类型要一致,否则某些数据库(如 PostgreSQL)会报
column "q1" has type integer but expression has type text - 没匹配到的
CASE默认返回NULL,所以SUM()不会出错,但COUNT()会漏计数,需改用COUNT(CASE WHEN ... THEN 1 END)
UNPIVOT 的等价替换:用 UNION ALL 拆行
UNPIVOT 的作用是把宽表变长表,比如把 q1, q2, q3 三列压成 quarter 和 amount 两列。没有原生支持时,最稳的方式是手写 UNION ALL。
对应上面的聚合结果,反向展开写法如下:
SELECT product, 'Q1' AS quarter, q1 AS amount FROM t WHERE q1 IS NOT NULL UNION ALL SELECT product, 'Q2', q2 FROM t WHERE q2 IS NOT NULL UNION ALL SELECT product, 'Q3', q3 FROM t WHERE q3 IS NOT NULL UNION ALL SELECT product, 'Q4', q4 FROM t WHERE q4 IS NOT NULL;
- 每条
SELECT必须字段数、顺序、类型完全一致,否则UNION ALL会失败 - 显式加
WHERE xxx IS NOT NULL可避免把NULL当有效数据展开(除非业务真需要) - 列数越多,
UNION ALL语句越长,维护成本高;此时应考虑是否该在应用层处理,而非硬怼 SQL
别碰“动态列名”这个坑
有人想用 EXECUTE FORMAT(PostgreSQL)或预处理语句(MySQL)拼出列名,实现“自动识别维度值并转列”。这看似灵活,实则危险。
- SQL 解析阶段无法预知列结构,导致 ORM、连接池、查询缓存全部失效
- 拼接字符串极易引入 SQL 注入,尤其当维度值来自用户输入(如
status字段含单引号) - 执行计划无法复用,每次生成新计划,高并发下 CPU 直接拉满
- 几乎所有 BI 工具(Tableau、Superset)都不支持运行时动态列,前端会报
column not found
真有不定维度需求,优先在应用层做两步:先查出所有可能的 category 值,再生成对应 CASE WHEN 的 SQL 字符串——而不是让数据库自己猜。










