PIVOT是SQL Server中用于行转列的表运算符,必须配合SELECT和子查询使用,语法为:SELECT...FROM(子查询)AS src PIVOT(聚合函数(列)FOR列IN(值列表))AS pvt,要求聚合函数必填、FOR列须为单列且IN中值为字面量、NULL被自动过滤、不支持动态列名(需动态SQL拼接)、仅限SQL Server/Azure SQL。

PIVOT 语法结构和基本用法
SQL Server 的 PIVOT 不是通用函数,而是一种表运算符,必须配合 SELECT 和子查询(或派生表)使用。它不能直接写在 WHERE 或 ORDER BY 后面,也不能嵌套在其他聚合函数里调用。
核心结构是:SELECT ... FROM (源数据子查询) AS src PIVOT (聚合函数(列) FOR 列 IN (值列表)) AS pvt。其中 聚合函数 必须存在(哪怕只是 MAX()),FOR 后的列必须是单列、非表达式,IN 中的值必须是字面量或明确枚举值——不能是子查询或变量。
-
IN里的值必须与FOR列的实际数据类型一致,比如该列是VARCHAR(10),就不能写IN ('A', 1) - 如果
FOR列有 NULL 值,它们会被自动过滤,不会出现在结果列中 -
PIVOT会隐式去重:同一组GROUP BY键 + 同一IN值,只保留一个聚合结果
常见错误:动态列名无法直接用 PIVOT 实现
很多人卡在“列名要根据数据动态生成”这个需求上。但 PIVOT 的 IN 子句不接受变量或结果集,硬写 IN (@cols) 会报错 Incorrect syntax near '@cols'。
真正可行的做法是拼接 SQL 字符串再 EXEC 或 sp_executesql。例如先查出所有需要的类别:SELECT STRING_AGG(QUOTENAME(Category), ',') FROM (SELECT DISTINCT Category FROM Sales) AS t,再把结果拼进 PIVOT 语句里。
- 拼接时务必用
QUOTENAME()包裹列名,防止注入或非法字符(如空格、连字符)导致语法错误 - 动态 SQL 中的聚合字段(如
SUM(Amount))和FOR列(如Category)必须来自同一数据源,且不能在子查询里被重命名成别名后又在PIVOT外引用 - SQL Server 2016+ 可用
STRING_AGG,旧版本得用FOR XML PATH拼接
替代方案:CASE + GROUP BY 更可控
当列数固定或可预估时,CASE WHEN 配合 GROUP BY 往往比 PIVOT 更直观、易调试,也更容易加条件过滤或处理 NULL。
例如按年份汇总销售额:SELECT Product, SUM(CASE WHEN Year = 2021 THEN Amount END) AS [2021], SUM(CASE WHEN Year = 2022 THEN Amount END) AS [2022] FROM Sales GROUP BY Product。
- 每个
CASE分支天然支持 WHERE 条件变形,比如CASE WHEN Year = 2022 AND Region = 'North' THEN Amount - 不需要额外子查询包裹,逻辑扁平;出错时能准确定位到某一行
CASE - 聚合函数可自由切换(
COUNT、AVG、MAX),而PIVOT要求统一聚合方式
兼容性注意:PIVOT 仅限 SQL Server 和 Azure SQL
PostgreSQL、MySQL、SQLite、Oracle 均无原生 PIVOT 运算符。PostgreSQL 可用 crosstab()(需安装 tablefunc 扩展),MySQL 常靠 GROUP_CONCAT + 应用层解析,Oracle 用 PIVOT 关键字但语法细节不同(比如不强制要求子查询别名)。
跨数据库项目中,若强行抽象 PIVOT 逻辑,大概率会在迁移时失败。更稳妥的做法是把行转列逻辑下沉到应用代码,或统一用 CASE 方案——它在所有主流 SQL 引擎中都有效。
真正麻烦的不是语法本身,而是业务侧常把“列名来自配置表”和“实时响应新维度”当成默认需求,而这恰恰是 PIVOT 最难兜住的部分。

















