在具有复合键(多列)的查询上使用交叉表

Using crosstab on a query with a composite key (multiple columns)

我最近从 SQL 服务器切换到 PostgreSQL 并试图找到等效的数据透视函数。我无法使用交叉表获得所需的输出,而我可以使用 SQL Server.

示例数据。

CREATE TABLE loc
AS
  SELECT location, sub_location, step, amount
  FROM ( VALUES
    ( 100 , '100_A', 'step_1', 2 ),
    ( 100 , '100_A', 'step_2', 7 ),
    ( 100 , '100_A', 'step_3', 6 ),
    ( 100 , '100_B', 'step_1', 5 ),
    ( 100 , '100_B', 'step_2', 8 ),
    ( 100 , '100_B', 'step_3', 9 )
  ) AS t(location, sub_location, step, amount);

我正在尝试实现以下结果集。

Location    Sub_location    Step_1  Step_2  Step_3
--------    ------------    ------  ------  ------
100         100_A           2       7       6
100         100_B           5       8       9

我可以轻松实现这是 MS SQL。还有我的交叉表查询,

Select * from crosstab
    (
     'select location, sub_location, step, amount from loc',
     'select distinct step from loc'
    )
    as final_result(location varchar,sub_location varchar, step_1 int, step_2 int, step_3 int);

我只看到一行而不是两行。无论如何要克服 postgres 中的这个限制。

使用ARRAY解决组合键问题

我认为您遇到的真正问题是您的 sub_location 是您的主要标识符(名称)的一部分,用于交叉目的。而且,不是 crosstab calls an extra column.

The "extra" columns are expected to be the same for all rows with the same row_name value.

所以本质上,构成名称的组合键必须由用户序列化。您仍然可以使用 ARRAY[location, sub_location]::text[].

将此作品序列化为 text[] 类型的 SQL ARRAY
SELECT *
FROM crosstab(
  $$ SELECT ARRAY[location, sub_location]::text[], step, amount FROM loc ORDER BY 1, 2, 3; $$,
  $$ SELECT DISTINCT step FROM loc ORDER BY 1; $$
) AS t(location text[], step_1 int, step_2 int, step_3 int );

  location   | step_1 | step_2 | step_3 
-------------+--------+--------+--------
 {100,100_A} |      2 |      7 |      6
 {100,100_B} |      5 |      8 |      9
(2 rows)

利用其中包含实际位置的子位置

现在,因为您的特定情况下的子位置包含位置数据,我们可以通过切换顺序来缩短它。我不会在 table 和 100_ 中存储子位置,但我们可以在这里使用它。需要明确的是,如果 location: 100, sublocation: 'A' 这是我存储它的方式,这将不起作用。

SELECT *
FROM crosstab(
  $$ SELECT sub_location, location, step, amount FROM loc ORDER BY 1, 2, 3; $$,
  $$ SELECT DISTINCT step FROM loc ORDER BY 1; $$
) AS t(sub_location text, location int, step_1 int, step_2 int, step_3 int );
 sub_location | location | step_1 | step_2 | step_3 
--------------+----------+--------+--------+--------
 100_A        |      100 |      2 |      7 |      6
 100_B        |      100 |      5 |      8 |      9
(2 rows)

虽然这消除了调用 ARRAY 的复杂性。

简化您的用例

我们也可以在此时删除 `location 或在父查询中切换顺序。

SELECT *
FROM crosstab(
  $$ SELECT sub_location, step, amount FROM loc ORDER BY 1, 2, 3; $$,
  $$ SELECT DISTINCT step FROM loc ORDER BY 1; $$
) AS t(location_full text, step_1 int, step_2 int, step_3 int );

 location_full | step_1 | step_2 | step_3 
---------------+--------+--------+--------
 100_A         |      2 |      7 |      6
 100_B         |      5 |      8 |      9
(2 rows)

不确定以上哪种方法最适合您。不要忘记 CREATE EXTENSION tablefunc; 当然,这是否比非交叉表版本更容易是完全主观的。