⌘+k ctrl+k
1.3 (稳定版)
搜索快捷键 cmd + k | ctrl + k
PIVOT 语句

PIVOT 语句允许将列中的不同值分离到各自的列中。这些新列中的值是使用聚合函数对与每个不同值匹配的行子集进行计算得出的。

DuckDB 实现了 SQL 标准 PIVOT 语法和简化的 PIVOT 语法,该语法可以在透视时自动检测要创建的列。PIVOT_WIDER 也可以用来代替 PIVOT 关键字。

有关 PIVOT 语句如何实现的详细信息,请参阅 PIVOT 内部结构站点

UNPIVOT 语句PIVOT 语句的逆操作。

简化的 PIVOT 语法

完整的语法图如下所示,但简化的 PIVOT 语法可以使用电子表格透视表的命名约定总结为:

PIVOT dataset
ON columns
USING values
GROUP BY rows
ORDER BY columns_with_order_directions
LIMIT number_of_rows;

ONUSINGGROUP BY 子句都是可选的,但不能全部省略。

示例数据

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

CREATE TABLE cities (
    country VARCHAR, name VARCHAR, year INTEGER, population INTEGER
);
INSERT INTO cities VALUES
    ('NL', 'Amsterdam', 2000, 1005),
    ('NL', 'Amsterdam', 2010, 1065),
    ('NL', 'Amsterdam', 2020, 1158),
    ('US', 'Seattle', 2000, 564),
    ('US', 'Seattle', 2010, 608),
    ('US', 'Seattle', 2020, 738),
    ('US', 'New York City', 2000, 8015),
    ('US', 'New York City', 2010, 8175),
    ('US', 'New York City', 2020, 8772);
SELECT *
FROM cities;
国家 名称 年份 人口
NL 阿姆斯特丹 2000 1005
NL 阿姆斯特丹 2010 1065
NL 阿姆斯特丹 2020 1158
US 西雅图 2000 564
US 西雅图 2010 608
US 西雅图 2020 738
US 纽约市 2000 8015
US 纽约市 2010 8175
US 纽约市 2020 8772

PIVOT ONUSING

使用下面的 PIVOT 语句为每一年创建一个单独的列,并计算每年的总人口。ON 子句指定要拆分为单独列的列。这相当于电子表格透视表中的列参数。

USING 子句确定如何聚合拆分为单独列的值。这相当于电子表格透视表中的值参数。如果未包含 USING 子句,则默认为 count(*)

PIVOT cities
ON year
USING sum(population);
国家 名称 2000 2010 2020
NL 阿姆斯特丹 1005 1065 1158
US 西雅图 564 608 738
US 纽约市 8015 8175 8772

在上述示例中,sum 聚合函数始终对单个值进行操作。如果只想改变数据的显示方向而不进行聚合,请使用 first 聚合函数。在此示例中,我们正在透视数值,但 first 函数非常适用于透视文本列。(这在电子表格透视表中很难实现,但在 DuckDB 中却很容易!)

此查询生成的结果与上述结果相同:

PIVOT cities
ON year
USING first(population);

注意:SQL 语法允许 USING 子句中的聚合函数带有 FILTER 子句。在 DuckDB 中,PIVOT 语句目前不支持这些,并且它们会被静默忽略。

PIVOT ONUSINGGROUP BY

默认情况下,PIVOT 语句保留所有未在 ONUSING 子句中指定的列。要只包含某些列并进一步聚合,请在 GROUP BY 子句中指定列。这相当于电子表格透视表中的行参数。

在下面的示例中,name 列不再包含在输出中,数据聚合到 country 级别。

PIVOT cities
ON year
USING sum(population)
GROUP BY country;
国家 2000 2010 2020
NL 1005 1065 1158
US 8579 8783 9510

ON 子句的 IN 过滤器

要在 ON 子句中只为列中的特定值创建单独的列,请使用可选的 IN 表达式。例如,假设我们想(没有特别的原因)忽略 2020 年……

PIVOT cities
ON year IN (2000, 2010)
USING sum(population)
GROUP BY country;
国家 2000 2010
NL 1005 1065
US 8579 8783

每个子句的多个表达式

ONGROUP BY 子句中可以指定多个列,USING 子句中可以包含多个聚合表达式。

多个 ON 列和 ON 表达式

可以将多个列透视到它们自己的列中。DuckDB 将查找每个 ON 子句列中的不同值,并为这些值的所有组合(笛卡尔积)创建一个新列。

在下面的示例中,国家和城市的每个唯一组合都会获得一个自己的列。某些组合可能不存在于底层数据中,因此这些列将填充 NULL 值。

PIVOT cities
ON country, name
USING sum(population);
年份 NL_Amsterdam NL_New York City NL_Seattle US_Amsterdam US_New York City US_Seattle
2000 1005 NULL NULL NULL 8015 564
2010 1065 NULL NULL NULL 8175 608
2020 1158 NULL NULL NULL 8772 738

若要仅透视底层数据中存在的值组合,请在 ON 子句中使用表达式。可以提供多个表达式和/或列。

在这里,countryname 被连接在一起,生成的连接结果各自分配一个列。可以使用任何任意的非聚合表达式。在这种情况下,使用下划线连接是为了模仿 PIVOT 子句在提供多个 ON 列时(如前一个示例中)所使用的命名约定。

PIVOT cities
ON country || '_' || name
USING sum(population);
年份 NL_Amsterdam US_New York City US_Seattle
2000 1005 8015 564
2010 1065 8175 608
2020 1158 8772 738

多个 USING 表达式

USING 子句中的每个表达式也可以包含别名。它将附加到生成的列名后面,并以一个下划线 (_) 连接。这使得当 USING 子句中包含多个表达式时,列命名约定更加清晰。

在此示例中,人口列的 summax 都针对每一年进行计算,并拆分为单独的列。

PIVOT cities
ON year
USING sum(population) AS total, max(population) AS max
GROUP BY country;
国家 2000_total 2000_max 2010_total 2010_max 2020_total 2020_max
US 8579 8015 8783 8175 9510 8772
NL 1005 1005 1065 1065 1158 1158

多个 GROUP BY

也可以提供多个 GROUP BY 列。请注意,必须使用列名而不是列位置(1、2 等),并且 GROUP BY 子句中不支持表达式。

PIVOT cities
ON year
USING sum(population)
GROUP BY country, name;
国家 名称 2000 2010 2020
NL 阿姆斯特丹 1005 1065 1158
US 西雅图 564 608 738
US 纽约市 8015 8175 8772

SELECT 语句中使用 PIVOT

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

在 CTE 中不需要 SELECTPIVOT 关键字可以被认为是取代了它的位置。

WITH pivot_alias AS (
    PIVOT cities
    ON year
    USING sum(population)
    GROUP BY country
)
SELECT * FROM pivot_alias;

PIVOT 可以用在子查询中,并且必须用括号括起来。请注意,此行为与 SQL 标准 Pivot 不同,如后续示例所示。

SELECT *
FROM (
    PIVOT cities
    ON year
    USING sum(population)
    GROUP BY country
) pivot_alias;

多个 PIVOT 语句

每个 PIVOT 都可以被视为一个 SELECT 节点,因此它们可以连接在一起或以其他方式进行操作。

例如,如果两个 PIVOT 语句共享相同的 GROUP BY 表达式,则可以使用 GROUP BY 子句中的列将它们连接起来,形成一个更宽的透视。

SELECT *
FROM (PIVOT cities ON year USING sum(population) GROUP BY country) year_pivot
JOIN (PIVOT cities ON name USING sum(population) GROUP BY country) name_pivot
USING (country);
国家 2000 2010 2020 阿姆斯特丹 纽约市 西雅图
NL 1005 1065 1158 3228 NULL NULL
US 8579 8783 9510 NULL 24962 1910

简化的 PIVOT 完整语法图

下面是 PIVOT 语句的完整语法图。

SQL 标准 PIVOT 语法

完整的语法图如下所示,但 SQL 标准 PIVOT 语法可以总结为:

SELECT *
FROM dataset
PIVOT (
    values
    FOR
        column_1 IN (in_list)
        column_2 IN (in_list)
        ...
    GROUP BY rows
);

与简化语法不同,IN 子句必须为每个要透视的列指定。如果您对动态透视感兴趣,建议使用简化语法。

请注意,FOR 子句中的表达式之间没有逗号分隔,但 valueGROUP BY 表达式必须用逗号分隔!

示例

此示例使用单个值表达式、单个列表达式和单个行表达式

SELECT *
FROM cities
PIVOT (
    sum(population)
    FOR
        year IN (2000, 2010, 2020)
    GROUP BY country
);
国家 2000 2010 2020
NL 1005 1065 1158
US 8579 8783 9510

此示例有点人为设计,但它提供了一个在 FOR 子句中使用多个值表达式和多个列的示例。

SELECT *
FROM cities
PIVOT (
    sum(population) AS total,
    count(population) AS count
    FOR
        year IN (2000, 2010)
        country IN ('NL', 'US')
);
名称 2000_NL_total 2000_NL_count 2000_US_total 2000_US_count 2010_NL_total 2010_NL_count 2010_US_total 2010_US_count
阿姆斯特丹 1005 1 NULL 0 1065 1 NULL 0
西雅图 NULL 0 564 1 NULL 0 608 1
纽约市 NULL 0 8015 1 NULL 0 8175 1

SQL 标准 PIVOT 完整语法图

下面是 SQL 标准版 PIVOT 语句的完整语法图。

限制

PIVOT 目前只接受聚合函数,不允许使用表达式。例如,以下查询尝试获取人口数量(即,不是 564,而是 564000),而不是以千为单位的人口数量:

PIVOT cities
ON year
USING sum(population) * 1000;

但是,它会因以下错误而失败:

Catalog Error:
* is not an aggregate function

为了解决这个限制,只使用聚合执行 PIVOT,然后使用 COLUMNS 表达式

SELECT country, name, 1000 * COLUMNS(* EXCLUDE (country, name))
FROM (
    PIVOT cities
    ON year
    USING sum(population)
);