跳到主要内容

Pivot 内部实现

PIVOT

Pivoting 通过 SQL 重写与专用 PhysicalPivot 算子组合实现,以获得更高性能。 每个 PIVOT 会先被实现为“聚合到列表”,再由 PhysicalPivot 将这些列表转换为最终列名与列值。 若 pivot 目标列需动态检测(即未使用 IN 子句),还需要额外预处理步骤。

Goose 与大多数 SQL 引擎一样,要求在查询开始时已知所有列名与类型。 为自动检测 PIVOT 结果列,语句会被拆解并重写为多个查询。 系统会使用 ENUM 类型 找到应成为列名的 distinct 值, 再把这些 ENUM 注入到 PIVOTIN 子句中。

IN 子句填充完成后,查询会再次被重写为“聚合到列表”的形式。

例如:

PIVOT cities
ON year
USING sum(population);

首先会被翻译为:

CREATE TEMPORARY TYPE __pivot_enum_0_0 AS ENUM (
SELECT DISTINCT
year::VARCHAR
FROM cities
ORDER BY
year
);
PIVOT cities
ON year IN __pivot_enum_0_0
USING sum(population);

最后翻译为:

SELECT country, name, list(year), list(population_sum)
FROM (
SELECT country, name, year, sum(population) AS population_sum
FROM cities
GROUP BY ALL
)
GROUP BY ALL;

该步骤会得到如下结果:

countrynamelist("year")list(population_sum)
NLAmsterdam[2000, 2010, 2020][1005, 1065, 1158]
USSeattle[2000, 2010, 2020][564, 608, 738]
USNew York City[2000, 2010, 2020][8015, 8175, 8772]

随后 PhysicalPivot 算子会把这些列表转成列名与列值,返回如下结果:

countryname200020102020
NLAmsterdam100510651158
USSeattle564608738
USNew York City801581758772

UNPIVOT

内部实现

Unpivot 全部通过 SQL 重写实现。 每个 UNPIVOT 会被重写为一组 unnest 函数,分别作用于“列名列表”和“列值列表”。 若是动态 unpivot,会先求值 COLUMNS 表达式得到列列表。

例如:

UNPIVOT monthly_sales
ON jan, feb, mar, apr, may, jun
INTO
NAME month
VALUE sales;

会被翻译为:

SELECT
empid,
dept,
unnest(['jan', 'feb', 'mar', 'apr', 'may', 'jun']) AS month,
unnest(["jan", "feb", "mar", "apr", "may", "jun"]) AS sales
FROM monthly_sales;

注意:单引号用于构造文本列表以填充 month,双引号用于读取列值填充 sales。 其结果与初始示例一致:

empiddeptmonthsales
1electronicsjan1
1electronicsfeb2
1electronicsmar3
1electronicsapr4
1electronicsmay5
1electronicsjun6
2clothesjan10
2clothesfeb20
2clothesmar30
2clothesapr40
2clothesmay50
2clothesjun60
3carsjan100
3carsfeb200
3carsmar300
3carsapr400
3carsmay500
3carsjun600