UNPIVOT
语句允许多列堆叠成更少的列。
在基本情况下,多列被堆叠成两列:一个 NAME
列(包含源列的名称)和一个 VALUE
列(包含源列的值)。
DuckDB 实现了 SQL 标准的 UNPIVOT
语法和简化的 UNPIVOT
语法。
两者都可以利用 COLUMNS
表达式 自动检测要解透视的列。
PIVOT_LONGER
也可以用来代替 UNPIVOT
关键字。
有关UNPIVOT
语句如何实现的详细信息,请参阅Pivot Internals 网站。
PIVOT
语句 是UNPIVOT
语句的逆操作。
简化的 UNPIVOT
语法
完整的语法图如下,但简化的UNPIVOT
语法可以使用电子表格数据透视表的命名约定总结为:
UNPIVOT ⟨dataset⟩
ON ⟨column(s)⟩
INTO
NAME ⟨name-column-name⟩
VALUE ⟨value-column-name(s)⟩
ORDER BY ⟨column(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;
员工编号 | 部门 | 一月 | 二月 | 三月 | 四月 | 五月 | 六月 |
---|---|---|---|---|---|---|---|
1 | 电子产品 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 衣服 | 10 | 20 | 30 | 40 | 50 | 60 |
3 | 汽车 | 100 | 200 | 300 | 400 | 500 | 600 |
UNPIVOT
手动
最典型的UNPIVOT
转换是将已经透视的数据重新堆叠成每列一个名称和一个值。
在这种情况下,所有月份将被堆叠到一个month
列和一个sales
列中。
UNPIVOT monthly_sales
ON jan, feb, mar, apr, may, jun
INTO
NAME month
VALUE sales;
empid | dept | month | sales |
---|---|---|---|
1 | 电子产品 | 一月 | 1 |
1 | 电子产品 | 二月 | 2 |
1 | 电子产品 | 三月 | 3 |
1 | 电子产品 | 四月 | 4 |
1 | 电子产品 | 五月 | 5 |
1 | 电子产品 | 六月 | 6 |
2 | 衣服 | 一月 | 10 |
2 | 衣服 | 二月 | 20 |
2 | 衣服 | 三月 | 30 |
2 | 衣服 | 四月 | 40 |
2 | 衣服 | 五月 | 50 |
2 | 衣服 | 六月 | 60 |
3 | 汽车 | 一月 | 100 |
3 | 汽车 | 二月 | 200 |
3 | 汽车 | 三月 | 300 |
3 | 汽车 | 四月 | 400 |
3 | 汽车 | 五月 | 500 |
3 | 汽车 | 六月 | 600 |
UNPIVOT
动态使用列表达式
在许多情况下,要取消透视的列数不容易预先确定。
在这个数据集的例子中,每次添加新月份时,上面的查询都必须更改。
COLUMNS
表达式 可以用来选择所有不是 empid
或 dept
的列。
这使得动态取消透视成为可能,无论添加了多少个月份,它都能正常工作。
下面的查询返回与上面相同的结果。
UNPIVOT monthly_sales
ON COLUMNS(* EXCLUDE (empid, dept))
INTO
NAME month
VALUE sales;
empid | dept | month | sales |
---|---|---|---|
1 | electronics | Jan | 1 |
1 | electronics | Feb | 2 |
1 | electronics | Mar | 3 |
1 | electronics | Apr | 4 |
1 | electronics | May | 5 |
1 | electronics | Jun | 6 |
2 | clothes | Jan | 10 |
2 | 衣服 | 二月 | 20 |
2 | 衣服 | 三月 | 30 |
2 | clothes | Apr | 40 |
2 | clothes | May | 50 |
2 | clothes | Jun | 60 |
3 | 汽车 | 一月 | 100 |
3 | cars | Feb | 200 |
3 | 汽车 | 三月 | 300 |
3 | 汽车 | 四月 | 400 |
3 | 汽车 | 五月 | 500 |
3 | 汽车 | 六月 | 600 |
UNPIVOT
转换为多值列
UNPIVOT
语句具有额外的灵活性:支持超过2个目标列。
当目标是减少数据集的透视程度,但不完全堆叠所有透视列时,这可能很有用。
为了演示这一点,下面的查询将生成一个数据集,其中每个季度内的月份数(第1、2或3个月)有一个单独的列,每个季度有一个单独的行。
由于季度比月份少,这确实使数据集变长,但没有上面那么长。
为了实现这一点,ON
子句中包含了多组列。
q1
和 q2
别名是可选的。
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;
员工编号 | 部门 | 季度 | 第一个月销售额 | 第二个月销售额 | 第三个月销售额 |
---|---|---|---|---|---|
1 | 电子产品 | q1 | 1 | 2 | 3 |
1 | 电子产品 | q2 | 4 | 5 | 6 |
2 | 衣服 | q1 | 10 | 20 | 30 |
2 | 衣服 | q2 | 40 | 50 | 60 |
3 | 汽车 | 第一季度 | 100 | 200 | 300 |
3 | 汽车 | q2 | 400 | 500 | 600 |
在 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
语句中的表达式
DuckDB 允许在 UNPIVOT
语句中使用表达式,前提是这些表达式仅涉及单个列。这些表达式可用于执行计算以及 显式类型转换。例如:
UNPIVOT
(SELECT 42 AS col1, 'woot' AS col2)
ON
(col1 * 2)::VARCHAR,
col2;
名称 | 值 |
---|---|
col1 | 84 |
col2 | woot |
简化的 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)
);
empid | dept | month | sales |
---|---|---|---|
1 | electronics | Jan | 1 |
1 | electronics | Feb | 2 |
1 | electronics | Mar | 3 |
1 | electronics | Apr | 4 |
1 | electronics | May | 5 |
1 | electronics | Jun | 6 |
2 | clothes | Jan | 10 |
2 | clothes | Feb | 20 |
2 | clothes | Mar | 30 |
2 | clothes | Apr | 40 |
2 | clothes | May | 50 |
2 | clothes | Jun | 60 |
3 | cars | Jan | 100 |
3 | cars | Feb | 200 |
3 | cars | Mar | 300 |
3 | cars | Apr | 400 |
3 | cars | May | 500 |
3 | cars | Jun | 600 |
SQL 标准 UNPIVOT
动态使用 COLUMNS
表达式
COLUMNS
表达式 可以用于动态确定 IN
列表中的列。
即使向数据集中添加了额外的 month
列,它仍然可以继续工作。
它产生与上述查询相同的结果。
FROM monthly_sales UNPIVOT (
sales
FOR month IN (columns(* EXCLUDE (empid, dept)))
);
SQL标准 UNPIVOT
转换为多值列
The UNPIVOT
statement has additional flexibility: more than 2 destination columns are supported.
This can be useful when the goal is to reduce the extent to which a dataset is pivoted, but not completely stack all pivoted columns.
To demonstrate this, the query below will generate a dataset with a separate column for the number of each month within the quarter (month 1, 2, or 3), and a separate row for each quarter.
Since there are fewer quarters than months, this does make the dataset longer, but not as long as the above.
为了实现这一点,UNPIVOT
语句的value-column-name
部分包含了多个列。
IN
子句中包含了多组列。
q1
和q2
别名是可选的。
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
)
);
员工编号 | 部门 | 季度 | 第一个月销售额 | 第二个月销售额 | 第三个月销售额 |
---|---|---|---|---|---|
1 | electronics | q1 | 1 | 2 | 3 |
1 | 电子产品 | q2 | 4 | 5 | 6 |
2 | 衣服 | q1 | 10 | 20 | 30 |
2 | clothes | q2 | 40 | 50 | 60 |
3 | 汽车 | 第一季度 | 100 | 200 | 300 |
3 | 汽车 | q2 | 400 | 500 | 600 |
SQL标准 UNPIVOT
完整语法图
以下是SQL标准版本的UNPIVOT
语句的完整语法图。