跳到主要内容

UNPIVOT 语句

UNPIVOT 语句可以将多列堆叠为更少的列。 在最基本的情况下,多列会堆叠为两列:NAME 列(包含源列名)和 VALUE 列(包含源列值)。

Goose 同时实现了 SQL 标准 UNPIVOT 语法和简化版 UNPIVOT 语法。 两者都可以使用 COLUMNS 表达式 自动检测要反透视的列。 也可以使用 PIVOT_LONGER 关键字来替代 UNPIVOT

关于 UNPIVOT 语句的实现细节,请参见 Pivot Internals site

PIVOT 语句UNPIVOT 语句的逆操作。

简化版 UNPIVOT 语法

完整语法图见下方,简化版 UNPIVOT 语法可按电子表格透视表的命名习惯概括为:

UNPIVOT ⟨dataset⟩
ONcolumn(s)
INTO
NAME ⟨name_column_name⟩
VALUE ⟨value_column_name(s)
ORDER BYcolumn(s)_with_order_direction(s)
LIMIT ⟨number_of_rows⟩;

示例数据

所有示例均使用以下查询生成的数据集:

CREATE OR REPLACE TABLE monthly_sales
(empid INTEGER, dept TEXT, Jan INTEGER, Feb INTEGER, Mar INTEGER, Apr INTEGER, May INTEGER, Jun INTEGER);
INSERT INTO monthly_sales VALUES
(1, 'electronics', 1, 2, 3, 4, 5, 6),
(2, 'clothes', 10, 20, 30, 40, 50, 60),
(3, 'cars', 100, 200, 300, 400, 500, 600);
FROM monthly_sales;
empiddeptJanFebMarAprMayJun
1electronics123456
2clothes102030405060
3cars100200300400500600

手动 UNPIVOT

最常见的 UNPIVOT 转换,是将已经透视过的数据重新堆叠为“名称列 + 值列”。 在这里,所有月份会被堆叠到 month 列和 sales 列中。

UNPIVOT monthly_sales
ON jan, feb, mar, apr, may, jun
INTO
NAME month
VALUE sales;
empiddeptmonthsales
1electronicsJan1
1electronicsFeb2
1electronicsMar3
1electronicsApr4
1electronicsMay5
1electronicsJun6
2clothesJan10
2clothesFeb20
2clothesMar30
2clothesApr40
2clothesMay50
2clothesJun60
3carsJan100
3carsFeb200
3carsMar300
3carsApr400
3carsMay500
3carsJun600

使用 COLUMNS 表达式动态 UNPIVOT

在很多情况下,要反透视的列数并不容易提前确定。 对于此数据集而言,每新增一个月份,上面的查询都需要修改。 可使用 COLUMNS 表达式 选取除 empiddept 之外的所有列。 这使得反透视可动态适配新增月份数量。 下方查询与上方查询返回的结果完全一致。

UNPIVOT monthly_sales
ON COLUMNS(* EXCLUDE (empid, dept))
INTO
NAME month
VALUE sales;
empiddeptmonthsales
1electronicsJan1
1electronicsFeb2
1electronicsMar3
1electronicsApr4
1electronicsMay5
1electronicsJun6
2clothesJan10
2clothesFeb20
2clothesMar30
2clothesApr40
2clothesMay50
2clothesJun60
3carsJan100
3carsFeb200
3carsMar300
3carsApr400
3carsMay500
3carsJun600

UNPIVOT 输出到多个值列

UNPIVOT 语句还有更高的灵活性:支持超过 2 个目标列。 当目标是“降低数据集透视程度”,但又不希望把所有透视列全部堆叠时,这种能力很有用。 为演示这一点,下方查询会生成一个数据集:将季度内每个月份编号(第 1、2、3 个月)拆分为独立列,并为每个季度生成独立行。 由于季度数少于月份数,数据集会变长,但不会像上面的示例那样长。

要实现这一点,需要在 ON 子句中包含多组列。 q1q2 别名是可选的。 ON 子句中每组列的列数,必须与 VALUE 子句中的列数一致。

UNPIVOT monthly_sales
ON (jan, feb, mar) AS q1, (apr, may, jun) AS q2
INTO
NAME quarter
VALUE month_1_sales, month_2_sales, month_3_sales;
empiddeptquartermonth_1_salesmonth_2_salesmonth_3_sales
1electronicsq1123
1electronicsq2456
2clothesq1102030
2clothesq2405060
3carsq1100200300
3carsq2400500600

SELECT 语句中使用 UNPIVOT

UNPIVOT 语句可以在 SELECT 语句中作为 CTE(通用表表达式,即 WITH 子句)或子查询使用。 这允许你将 UNPIVOT 与其他 SQL 逻辑结合,也可在同一查询中使用多个 UNPIVOT

在 CTE 内无需再写 SELECT,可将 UNPIVOT 关键字理解为其占位替代。

WITH unpivot_alias AS (
UNPIVOT monthly_sales
ON COLUMNS(* EXCLUDE (empid, dept))
INTO
NAME month
VALUE sales
)
SELECT * FROM unpivot_alias;

UNPIVOT 也可用于子查询,并且必须用括号包裹。 请注意,这一行为与 SQL 标准 UNPIVOT 不同,后续示例会说明差异。

SELECT *
FROM (
UNPIVOT monthly_sales
ON COLUMNS(* EXCLUDE (empid, dept))
INTO
NAME month
VALUE sales
) unpivot_alias;

UNPIVOT 语句中的表达式

Goose 允许在 UNPIVOT 语句中使用表达式,前提是表达式只涉及单个列。它们既可用于计算,也可用于显式类型转换。例如:

UNPIVOT
(SELECT 42 AS col1, 'woot' AS col2)
ON
(col1 * 2)::VARCHAR,
col2;
namevalue
col184
col2woot

简化版 UNPIVOT 完整语法图

下方是 UNPIVOT 语句的完整语法图。

SQL 标准 UNPIVOT 语法

完整语法图见下方,SQL 标准 UNPIVOT 语法可概括为:

FROM [dataset]
UNPIVOT [INCLUDE NULLS] (
[value-column-name(s)]
FOR [name-column-name] IN [column(s)]
);

请注意,name-column-name 表达式中只能包含一个列。

手动编写 SQL 标准 UNPIVOT

使用 SQL 标准语法完成基础 UNPIVOT 操作时,只需少量额外语法。

FROM monthly_sales UNPIVOT (
sales
FOR month IN (jan, feb, mar, apr, may, jun)
);
empiddeptmonthsales
1electronicsJan1
1electronicsFeb2
1electronicsMar3
1electronicsApr4
1electronicsMay5
1electronicsJun6
2clothesJan10
2clothesFeb20
2clothesMar30
2clothesApr40
2clothesMay50
2clothesJun60
3carsJan100
3carsFeb200
3carsMar300
3carsApr400
3carsMay500
3carsJun600

使用 COLUMNS 表达式动态执行 SQL 标准 UNPIVOT

COLUMNS 表达式 可用于动态确定 IN 子句中的列列表。 即使数据集中新增了 month 列,该写法仍可继续工作。 其结果与上方查询相同。

FROM monthly_sales UNPIVOT (
sales
FOR month IN (columns(* EXCLUDE (empid, dept)))
);

SQL 标准 UNPIVOT 输出到多个值列

UNPIVOT 语句还具备额外灵活性:支持超过 2 个目标列。 当目标是减少数据集的透视程度,但不希望把全部透视列完全堆叠时,这很有用。 为演示这一点,下方查询会生成一个数据集:将季度内每个月份编号(第 1、2、3 个月)拆分为独立列,并为每个季度生成独立行。 由于季度数少于月份数,数据集会变长,但不会像前面的示例那样长。

要实现这一点,需要在 UNPIVOTvalue-column-name 部分包含多个列。 IN 子句中也要包含多组列。 q1q2 别名是可选的。 IN 子句中每组列的列数必须与 value-column-name 部分的列数一致。

FROM monthly_sales
UNPIVOT (
(month_1_sales, month_2_sales, month_3_sales)
FOR quarter IN (
(jan, feb, mar) AS q1,
(apr, may, jun) AS q2
)
);
empiddeptquartermonth_1_salesmonth_2_salesmonth_3_sales
1electronicsq1123
1electronicsq2456
2clothesq1102030
2clothesq2405060
3carsq1100200300
3carsq2400500600

SQL 标准 UNPIVOT 完整语法图

下方是 SQL 标准版本 UNPIVOT 语句的完整语法图。