⌘+k ctrl+k
1.4 (LTS)
搜索快捷键 cmd + k | ctrl + k
UNPIVOT 语句

UNPIVOT 语句允许将多个列堆叠为较少的列。在基本情况下,多个列被堆叠为两列:一个 NAME 列(包含源列的名称)和一个 VALUE 列(包含来自源列的值)。

DuckDB 实现了 SQL 标准的 UNPIVOT 语法和简化的 UNPIVOT 语法。两者都可以利用 COLUMNS 表达式来自动检测需要取消透视(unpivot)的列。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;
empid dept Jan Feb Mar Apr May Jun
1 electronics 1 2 3 4 5 6
2 clothes 10 20 30 40 50 60
3 cars 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 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

使用 COLUMNS 表达式动态 UNPIVOT

在许多情况下,需要取消透视的列数不容易提前确定。对于此数据集,上述查询每次添加新月份时都需要修改。COLUMNS 表达式可用于选择所有非 empiddept 的列。这实现了动态取消透视,无论添加多少个月份,它都能正常工作。下面的查询返回的结果与上面相同。

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 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

取消透视(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;
empid dept quarter month_1_sales month_2_sales month_3_sales
1 electronics q1 1 2 3
1 electronics q2 4 5 6
2 clothes q1 10 20 30
2 clothes q2 40 50 60
3 cars q1 100 200 300
3 cars q2 400 500 600

SELECT 语句中使用 UNPIVOT

UNPIVOT 语句可以作为 CTE(公用表表达式或 WITH 子句)或子查询包含在 SELECT 语句中。这允许 UNPIVOT 与其他 SQL 逻辑一起使用,并允许在单个查询中使用多个 UNPIVOT

CTE 内部不需要 SELECTUNPIVOT 关键字可以被视为取代了它的位置。

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;
name value
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

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

COLUMNS 表达式可用于动态确定 IN 列列表。即使向数据集添加了额外的 month 列,这也将继续有效。它产生的结果与上述查询相同。

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

取消透视(UNPIVOT)为多个值列的 SQL 标准实现

UNPIVOT 语句具有额外的灵活性:支持超过 2 个目标列。当目标是减少数据集的透视程度(而不是完全堆叠所有已透视的列)时,这非常有用。为了说明这一点,下面的查询将生成一个数据集,其中每个季度内的月份数(第 1、2 或 3 个月)都有一个单独的列,并且每个季度有一行。由于季度的数量少于月份的数量,这使数据集变长了,但没有上面那么长。

为实现这一点,多列被包含在 UNPIVOT 语句的 value-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
    )
);
empid dept quarter month_1_sales month_2_sales month_3_sales
1 electronics q1 1 2 3
1 electronics q2 4 5 6
2 clothes q1 10 20 30
2 clothes q2 40 50 60
3 cars q1 100 200 300
3 cars q2 400 500 600

SQL 标准 UNPIVOT 完整语法图

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

© 2025 DuckDB 基金会,阿姆斯特丹,荷兰
行为准则 商标使用指南