PIVOT结果全为NULL是因未匹配分组值,其本质是聚合+列名硬编码;FOR后必须是已存在列名,聚合函数不可省略,且不支持动态列名,需用动态SQL实现。
PIVOT 用法不对,结果全是 NULL?
pl/sql 中
不是万能转置工具,它默认做的是「聚合 + 列名硬编码」,没匹配上的分组值直接填
,不是漏数据,是逻辑本就如此。
常见错误现象:
后某列全为
,但源数据明明有对应值;或者报错
—— 多半是别名或聚合函数写错了位置。
的
子句后必须跟一个**已存在列名**(不能是表达式),且该列值会变成新列名
聚合函数(如
、
)必须显式写出,不能省略;不支持
直接套用
被转成列的原始值,必须能作为合法标识符(比如含空格、特殊字符会失败,需用双引号包裹,但不推荐)
Oracle 11g+ 才支持
,10g 及更早版本只能手写
示例:把销售记录按季度汇总成一行
UNPIVOT 报 ORA-56902:expecting an aggregate function
这个错误不是语法写错了,而是你把
当成了
的逆操作来“自动还原”,其实它只负责把多列拉成两列(
和
),不恢复原始结构,也不处理缺失值逻辑。
关键点在于:
要求所有待展开的列类型必须一致(或能隐式转换),且不能包含
值——除非显式加
。
漏写
时,
行直接被丢弃,容易误以为数据丢失
子句里列名必须用括号包裹,且不能带别名(如
,不是
)
目标列(
和
)名由你指定,但
列值来自原列名,Oracle 自动转成字符串,不支持自定义映射
如果原列类型混用(比如
和
),必须先统一类型,否则报错
示例:把季度汇总表拆回明细
动态列名场景下 PIVOT 怎么办?
本身不支持变量列名,
子句必须是字面量列表。想根据实际数据动态生成列(比如不同年份、不同部门),就得拼 SQL 字符串再执行——也就是动态 SQL。
这不是语法限制,是设计使然:Oracle 在解析阶段就要确定输出列结构,无法在运行时推导。
用
+ 游标查出所有可能的列值,拼成
字符串
必须用
执行,不能直接写在普通查询里
注意注入风险:所有拼入的列名必须经严格校验(比如只允许字母数字下划线),不能直接拼用户输入
性能上,每次执行都触发硬解析,不适合高频调用;若列值变化少,可考虑物化视图预计算
简单示意(不带校验):
替代方案:CASE WHEN 比 PIVOT 更可控?
当列数固定、逻辑复杂(比如要加条件过滤、多级聚合),或需要兼容老版本 Oracle 时,手写
实际更稳。
它没有
那些隐式规则,每列怎么算、空值怎么填,全由你控制;而且执行计划更透明,调试也方便。
支持任意表达式,比如
,而
只能按原值映射
聚合函数可以不同(某列用
,另一列用
),
要求全部一致
不需要提前知道所有列值,
就能兜底,不用怕新增分类导致结果变宽
缺点是写起来啰嗦,列一多 SQL 很长,但 IDE 里折叠一下就好
等价于前面 PIVOT 示例的手写法:
真正麻烦的不是语法,是搞清你要转的是「结构」还是「展示」——前者得靠建模或中间表,后者才轮到
或
出场。列名动态、数据稀疏、类型混杂,这几个点卡住一次,就得多绕半圈。
pivotnullPIVOTNULLORA-00904: "xxx": invalid identifierPIVOTFORMAXCOUNTSELECT *PIVOTCASE WHENSELECT *
FROM (SELECT product, quarter, amount FROM sales)
PIVOT (SUM(amount) FOR quarter IN ('Q1' AS q1, 'Q2' AS q2, 'Q3' AS q3, 'Q4' AS q4));UNPIVOTPIVOTnamevalueUNPIVOTNULLINCLUDE NULLSINCLUDE NULLSNULLIN(q1,q2,q3)(q1 AS Q1, ...)namevaluenameNUMBERVARCHAR2SELECT product, quarter, amount
FROM quarterly_summary
UNPIVOT INCLUDE NULLS (amount FOR quarter IN (q1 AS 'Q1', q2 AS 'Q2', q3 AS 'Q3', q4 AS 'Q4'));PIVOTINLISTAGGIN ('A','B','C')EXECUTE IMMEDIATEDECLARE
sql_str VARCHAR2(4000);
cols VARCHAR2(2000);
BEGIN
SELECT LISTAGG('''' || dept_name || ''' AS "' || dept_name || '"', ', ')
WITHIN GROUP (ORDER BY dept_name)
INTO cols
FROM (SELECT DISTINCT dept_name FROM emp);
sql_str := 'SELECT * FROM (SELECT dept_name, salary FROM emp) ' ||
'PIVOT (SUM(salary) FOR dept_name IN (' || cols || '))';
EXECUTE IMMEDIATE sql_str;
END;
CASE WHENPIVOTCASE WHENCASE WHEN salary > 10000 THEN 'HIGH' ELSE 'LOW' ENDPIVOTSUMCOUNTPIVOTELSE 0SELECT 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;PIVOTCASE