跳到主要内容

PIVOT 语句

PIVOT 语句允许将某一列中的不同值拆分为各自独立的列。 这些新列中的值通过聚合函数计算得到,聚合范围是与每个不同值匹配的那部分行。

Goose 同时实现了 SQL 标准 PIVOT 语法和简化版 PIVOT 语法;后者会在透视时自动检测要创建的列。 也可以使用 PIVOT_WIDER 代替 PIVOT 关键字。

有关 PIVOT 语句实现细节,请参见 Pivot Internals site

UNPIVOT 语句PIVOT 语句的逆操作。

简化版 PIVOT 语法

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

PIVOT ⟨dataset⟩
ONcolumns
USINGvalues
GROUP BYrows
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;
countrynameyearpopulation
NLAmsterdam20001005
NLAmsterdam20101065
NLAmsterdam20201158
USSeattle2000564
USSeattle2010608
USSeattle2020738
USNew York City20008015
USNew York City20108175
USNew York City20208772

PIVOT ONUSING

使用下面的 PIVOT 语句为每个年份创建单独列,并计算各年的总人口。 ON 子句用于指定要拆分成独立列的列。 它等价于电子表格数据透视表中的 columns 参数。

USING 子句决定如何聚合这些被拆分到独立列中的值。 这等价于电子表格数据透视表中的 values 参数。 如果未提供 USING 子句,默认使用 count(*)

PIVOT cities
ON year
USING sum(population);
countryname200020102020
NLAmsterdam100510651158
USSeattle564608738
USNew York City801581758772

在上面的示例中,sum 聚合始终作用于单个值。 如果我们只想改变数据的展示方向而不进行聚合,可使用 first 聚合函数。 此示例透视的是数值列,但 first 函数同样非常适合透视文本列。 (这在电子表格数据透视表中较难实现,但在 Goose 中很容易!)

这条查询会生成与上面相同的结果:

PIVOT cities
ON year
USING first(population);

注意:SQL 语法允许在 USING 子句中的聚合函数上使用 FILTER clauses。 但在 Goose 中,PIVOT 语句当前不支持这些子句,并会静默忽略。

PIVOT ONUSINGGROUP BY

默认情况下,PIVOT 语句会保留所有未在 ONUSING 子句中指定的列。 如果只想保留部分列并进一步聚合,请在 GROUP BY 子句中指定列。 这等价于电子表格数据透视表中的 rows 参数。

在下面的示例中,name 列不再出现在输出中,数据按 country 层级聚合。

PIVOT cities
ON year
USING sum(population)
GROUP BY country;
country200020102020
NL100510651158
US857987839510

ON 子句中的 IN 过滤

若只想为 ON 子句列中的特定值创建独立列,可使用可选的 IN 表达式。 例如,假设我们出于某种原因不考虑 2020 年...

PIVOT cities
ON year IN (2000, 2010)
USING sum(population)
GROUP BY country;
country20002010
NL10051065
US85798783

每个子句支持多个表达式

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

多个 ON 列与 ON 表达式

可以将多个列透视为各自独立的列。 Goose 会找出每个 ON 子句列中的不同值,并为这些值的全部组合(笛卡尔积)创建新列。

在下面的示例中,唯一国家与唯一城市的所有组合都会得到各自的列。 某些组合可能不存在于底层数据中,因此对应列会填充 NULL

PIVOT cities
ON country, name
USING sum(population);
yearNL_AmsterdamNL_New York CityNL_SeattleUS_AmsterdamUS_New York CityUS_Seattle
20001005NULLNULLNULL8015564
20101065NULLNULLNULL8175608
20201158NULLNULLNULL8772738

如果只想透视底层数据中真实存在的值组合,请在 ON 子句中使用表达式。 可提供多个表达式和/或列。

这里将 countryname 连接在一起,连接结果各自获得一列。 可以使用任意非聚合表达式。 此处使用下划线连接,是为了模拟在提供多个 ON 列时 PIVOT 子句使用的命名约定(如上一示例)。

PIVOT cities
ON country || '_' || name
USING sum(population);
yearNL_AmsterdamUS_New York CityUS_Seattle
200010058015564
201010658175608
202011588772738

多个 USING 表达式

USING 子句中的每个表达式也可以包含别名。 该别名会追加到生成列名后,并以下划线(_)分隔。 当 USING 子句包含多个表达式时,这会让列命名更清晰。

此示例中,对每一年都会计算 population 列的 summax,并拆分为独立列。

PIVOT cities
ON year
USING sum(population) AS total, max(population) AS max
GROUP BY country;
country2000_total2000_max2010_total2010_max2020_total2020_max
US857980158783817595108772
NL100510051065106511581158

多个 GROUP BY

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

PIVOT cities
ON year
USING sum(population)
GROUP BY country, name;
countryname200020102020
NLAmsterdam100510651158
USSeattle564608738
USNew York City801581758772

SELECT 语句中使用 PIVOT

PIVOT 语句可以作为 CTE(Common Table Expression,即 WITH 子句)或子查询包含在 SELECT 语句中。 这使得 PIVOT 可以与其他 SQL 逻辑一起使用,也允许在一条查询中使用多个 PIVOT

在 CTE 内不需要再写 SELECT,可以将 PIVOT 关键字视为它的替代。

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);
country200020102020AmsterdamNew York CitySeattle
NL1005106511583228NULLNULL
US857987839510NULL249621910

简化版 PIVOT 完整语法图

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

SQL 标准 PIVOT 语法

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

SELECT *
FROM ⟨dataset⟩
PIVOT (
values
FOR
⟨column_1⟩ IN (⟨in_list⟩)
⟨column_2⟩ IN (⟨in_list⟩)
...
GROUP BYrows
);

与简化语法不同,IN 子句必须为每个要透视的列分别指定。 如果你需要动态透视,推荐使用简化语法。

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

示例

这个示例使用了单个 value 表达式、单个列表达式和单个行表达式:

SELECT *
FROM cities
PIVOT (
sum(population)
FOR
year IN (2000, 2010, 2020)
GROUP BY country
);
country200020102020
NL100510651158
US857987839510

这个示例略显刻意,但可用于演示在 FOR 子句中使用多个 value 表达式和多个列。

SELECT *
FROM cities
PIVOT (
sum(population) AS total,
count(population) AS count
FOR
year IN (2000, 2010)
country IN ('NL', 'US')
);
name2000_NL_total2000_NL_count2000_US_total2000_US_count2010_NL_total2010_NL_count2010_US_total2010_US_count
Amsterdam10051NULL010651NULL0
SeattleNULL05641NULL06081
New York CityNULL080151NULL081751

SQL 标准 PIVOT 完整语法图

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

限制

PIVOT 当前仅接受聚合函数,不允许表达式。 例如,以下查询尝试将 population 从“千人”转换为“人数”(即把 564 变为 564000):

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

但它会报如下错误:

Catalog Error:
* is not an aggregate function

要绕过这一限制,可先仅执行聚合 PIVOT,再使用 COLUMNS expression

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