CREATE TABLE IF NOT EXISTS doc_pivot_tags (
order_id INT,
region STRING,
tags STRING
);
INSERT INTO doc_pivot_tags VALUES
(1, '华东', '产品A,产品B'),
(2, '华南', '产品A'),
(3, '华北', '产品B,产品C'),
(4, '华东', '产品A,产品C'),
(5, '华南', '产品B,产品C');
场景一:按月份透视各地区销售额
把月份展开为列,每个地区一行,横向对比各月销售额:
SELECT * FROM (
SELECT region, month, amount FROM doc_pivot_sales
)
PIVOT (
sum(amount)
FOR month IN ('2024-01' AS jan_amount, '2024-02' AS feb_amount, '2024-03' AS mar_amount)
)
ORDER BY region;
region
jan_amount
feb_amount
mar_amount
华东
2000.0
2450.0
2900.0
华北
1200.0
1470.0
1750.0
华南
1500.0
1850.0
2180.0
amount
amount
是 DOUBLE 类型,聚合结果显示为
2000.0
2000.0
这样的形式。
PIVOT
PIVOT
会把聚合列(
amount
amount
)和透视列(
month
month
)之外的所有列自动当作分组列,这里只剩
region
region
,所以每个地区一行。子查询的作用是只保留这三列:如果直接写
FROM doc_pivot_sales PIVOT (...)
FROM doc_pivot_sales PIVOT (...)
,
sale_id
sale_id
和
product
product
也会成为分组列,结果会变成 18 行,每行只有一个月有值。
AS jan_amount
AS jan_amount
为透视值指定列名。不写别名时,列名就是透视值本身(如
2024-01
2024-01
),引用时需要加反引号,因此建议总是写别名。
场景二:多个指标的交叉表
每种产品同时统计金额和订单数
PIVOT
PIVOT
中写多个聚合,每个透视值会生成多列,列名为“透视值别名_聚合别名”:
SELECT * FROM (
SELECT region, product, amount FROM doc_pivot_sales
)
PIVOT (
sum(amount) AS total, count(*) AS orders
FOR product IN ('产品A' AS a, '产品B' AS b)
)
ORDER BY region;
region
a_total
a_orders
b_total
b_orders
华东
4500.0
3
2850.0
3
华北
2550.0
3
1870.0
3
华南
3300.0
3
2230.0
3
列按“透视值优先”排列:先是产品 A 的两个指标,再是产品 B 的两个指标。
月份 × 地区矩阵并附加合计列
透视结果中的列可以在外层查询中继续计算,例如加一列合计:
SELECT month, east, south, north, east + south + north AS total
FROM (
SELECT month, region, amount FROM doc_pivot_sales
)
PIVOT (
sum(amount)
FOR region IN ('华东' AS east, '华南' AS south, '华北' AS north)
)
ORDER BY month;
SELECT * FROM (
SELECT region, month, product, amount FROM doc_pivot_sales
)
PIVOT (
sum(amount)
FOR (month, product) IN (('2024-01', '产品A') AS jan_a, ('2024-03', '产品B') AS mar_b)
)
ORDER BY region;
region
jan_a
mar_b
华东
1200.0
1100.0
华北
700.0
750.0
华南
900.0
880.0
不在
IN
IN
列表中的组合(如 2 月的所有数据)会被忽略。元组中的值个数必须与
FOR
FOR
后的列数一致。
场景四:把季度宽表还原为明细(UNPIVOT)
把
q1_amount
q1_amount
~
q4_amount
q4_amount
四列收拢为
quarter
quarter
(季度)和
amount
amount
(金额)两列,便于按季度统一聚合或绘制趋势图:
SELECT region, quarter, amount FROM doc_pivot_wide
UNPIVOT (
amount FOR quarter IN (q1_amount AS q1, q2_amount AS q2, q3_amount AS q3, q4_amount AS q4)
)
WHERE region IN ('华东', '西南')
ORDER BY region, quarter;
region
quarter
amount
华东
q1
2000.0
华东
q2
2450.0
华东
q3
2900.0
华东
q4
3100.0
西南
q1
800.0
西南
q2
950.0
amount FOR quarter
amount FOR quarter
:
amount
amount
是新生成的值列,
quarter
quarter
是新生成的名称列,内容为
IN
IN
中的别名。
没有出现在
IN
IN
中的列(
region
region
)原样保留。
西南的 Q3、Q4 为 NULL,默认被丢弃,所以只有 2 行。
统计“哪些季度还没有数据”时需要保留 NULL 行,加上
INCLUDE NULLS
INCLUDE NULLS
:
SELECT region, quarter, amount FROM doc_pivot_wide
UNPIVOT INCLUDE NULLS (
amount FOR quarter IN (q1_amount AS q1, q2_amount AS q2, q3_amount AS q3, q4_amount AS q4)
)
WHERE amount IS NULL
ORDER BY region, quarter;
region
quarter
amount
西南
q3
NULL
西南
q4
NULL
场景五:展开多值列
一个单元格里存了多个值(如逗号分隔的标签)时,用
SPLIT
SPLIT
拆成数组,再用
LATERAL VIEW EXPLODE
LATERAL VIEW EXPLODE
展开为多行:
SELECT order_id, region, tag
FROM doc_pivot_tags
LATERAL VIEW EXPLODE(SPLIT(tags, ',')) t AS tag
ORDER BY order_id, tag;
order_id
region
tag
1
华东
产品A
1
华东
产品B
2
华南
产品A
3
华北
产品B
3
华北
产品C
4
华东
产品A
4
华东
产品C
5
华南
产品B
5
华南
产品C
展开后的行数等于所有订单的标签数之和。展开后可以直接聚合,例如统计每种产品出现在多少个订单中:
SELECT tag AS product, count(*) AS order_count
FROM doc_pivot_tags
LATERAL VIEW EXPLODE(SPLIT(tags, ',')) t AS tag
GROUP BY tag
ORDER BY tag;
product
order_count
产品A
3
产品B
3
产品C
3
场景六:PIVOT 无法表达时改用 CASE WHEN
PIVOT
PIVOT
要求所有新列都来自同一个透视列、使用同一组聚合。如果每一列的条件各不相同,就需要用
CASE WHEN
CASE WHEN
条件聚合。例如一张地区报表同时需要“1 月产品 A 金额”“一季度总金额”和“产品 B 金额占比”:
SELECT
region,
sum(CASE WHEN month = '2024-01' AND product = '产品A' THEN amount ELSE 0 END) AS jan_a,
sum(amount) AS q1_total,
round(sum(CASE WHEN product = '产品B' THEN amount ELSE 0 END) / sum(amount), 4) AS b_ratio
FROM doc_pivot_sales
GROUP BY region
ORDER BY region;
region
jan_a
q1_total
b_ratio
华东
1200.0
7350.0
0.3878
华北
700.0
4420.0
0.4231
华南
900.0
5530.0
0.4033
三列分别按“月份 + 产品”“全部数据”“产品”筛选,没有共同的透视列,
PIVOT
PIVOT
写不出来。
两种写法的取舍:
需求
推荐写法
按一列(或一组列)的固定取值展开,所有列用同样的聚合
PIVOT
PIVOT
,写法短,新增取值只需改
IN
IN
列表
各列条件不同,或要混合不同维度的筛选
CASE WHEN
CASE WHEN
没有数据的组合要显示为 0 而不是 NULL
两种都可以:
CASE WHEN ... ELSE 0
CASE WHEN ... ELSE 0
,或对
PIVOT
PIVOT
结果套
coalesce
coalesce
清理测试数据
DROP TABLE IF EXISTS doc_pivot_sales;
DROP TABLE IF EXISTS doc_pivot_wide;
DROP TABLE IF EXISTS doc_pivot_tags;