如何使用列名旋转或 'merge' 行?

How to pivot or 'merge' rows with column names?

我有以下 table:

crit_id   | criterium  | val1  | val2
----------+------------+-------+--------
  1       |     T01    |   9   |   9
  2       |     T02    |   3   |   5
  3       |     T03    |   4   |   9
  4       |     T01    |   2   |   3
  5       |     T02    |   5   |   1
  6       |     T03    |   6   |   1

我需要将 'criterium' 中的值转换为具有 val1 和 val2 的 'cross product' 列。所以结果必须像这样:

T01_val1 |T01_val2 |T02_val1 |T02_val2 | T03_val1 | T03_val2
---------+---------+---------+---------+----------+---------
   9     |   9     |    3    |    5    |    4     |    9
   2     |   3     |    5    |    1    |    6     |    1

或者换句话说:我需要所有条件的每个值都在一行中。

这是我目前的做法:

select 
   case when criterium = 'T01' then val1 else null end as T01_val1,
   case when criterium = 'T01' then val2 else null end as T01_val2,
   case when criterium = 'T02' then val1 else null end as T02_val1,
   case when criterium = 'T02' then val2 else null end as T02_val2,
   case when criterium = 'T03' then val1 else null end as T03_val1,
   case when criterium = 'T03' then val2 else null end as T04_val2,
from crit_table;

但是结果看起来不是我想要的样子:

T01_val1 |T01_val2 |T02_val1 |T02_val2 | T03_val1 | T03_val2
---------+---------+---------+---------+----------+---------
   9     |   9     |  null   |  null   |  null    |  null
   null  |  null   |    3    |    5    |  null    |  null
   null  |  null   |  null   |  null   |   4      |    9

实现我的目标最快的方法是什么?

加分题:

我有 77 个标准,每个标准有七种不同的值。所以我要写 539 case 语句。动态创建它们的最佳方式是什么?

我正在使用 PostgreSql 9.4

DECLARE @Table1  TABLE 
    (crit_id int, criterium varchar(3), val1 int, val2 int)
;

INSERT INTO @Table1
    (crit_id, criterium, val1, val2)
VALUES
    (1, 'T01', 9, 9),
    (2, 'T02', 3, 5),
    (3, 'T03', 4, 9),
    (4, 'T01', 2, 3),
    (5, 'T02', 5, 1),
    (6, 'T03', 6, 1)
;

select [T01] As [T01_val1 ],[T01-1] As [T01_val2 ],[T02] As [T02_val1 ],[T02-1] As [T02_val2 ],[T03] As [T03_val1 ],[T03-1] As [T03_val3 ] from (
select T.criterium,T.val1,ROW_NUMBER()OVER(PARTITION BY T.criterium ORDER BY (SELECT NULL)) RN from (
select  criterium, val1 from @Table1
UNION ALL
select criterium+'-'+'1', val2 from @Table1)T)PP

PIVOT (MAX(val1) FOR criterium IN([T01],[T02],[T03],[T01-1],[T02-1],[T03-1]))P

我同意 Michael 的评论,即这个要求看起来有点奇怪,但如果您真的需要那样,那么您的解决方案就走在了正确的轨道上。它只需要一点额外的代码(以及 val_1 和 val_2 混淆的地方的小更正):

select 
   sum(case when criterium = 'T01' then val_1 else null end) as T01_val1,
   sum(case when criterium = 'T01' then val_2 else null end) as T01_val2,
   sum(case when criterium = 'T02' then val_1 else null end) as T02_val1,
   sum(case when criterium = 'T02' then val_2 else null end) as T02_val2,
   sum(case when criterium = 'T03' then val_1 else null end) as T03_val1,
   sum(case when criterium = 'T03' then val_2 else null end) as T03_val2
from 
   crit_table
group by 
   trunc((crit_id-1)/3.0)
order by 
  trunc((crit_id-1)/3.0);

其工作原理如下。要将您发布的结果聚合到您想要的结果中,第一个有用的观察结果是所需结果的行数少于您的初步结果。因此需要某种分组,关键问题是:"What's the grouping criterion?" 在这种情况下,它并不明显:它是标准 ID(减去 1,从 0 开始计数)除以 3,然后截断。三者来自不同标准的数量。解开这个谜题后,很容易看出,在聚合到同一结果行的输入行中,每一列只有一个非空值。这意味着聚合函数的选择并不是那么重要,因为它只需要 return 唯一的非空值。我在我的代码片段中使用了总和,但您也可以使用最小值或最大值。

至于奖励问题:使用代码生成器查询来生成您需要的查询。代码如下所示(为了保持简洁,只有三种类型的值):

with value_table as  /* possible kinds of values, add the remaining ones here */
  (select 'val_1' value_type union 
   select 'val_2' value_type union  
   select 'val_3' value_type )
select contents from (
      select 0 order_id, 'select' contents
      union
          select row_number() over () order_id, 
                 'max(case when criterium = '''||criterium||''' then '||value_type||' else null end) '||criterium||'_'||value_type||',' contents
            from crit_table
      cross join value_table
      union select 9999999 order_id, 
                   '    from crit_table  group by trunc((crit_id-1)/3.0) order by  trunc((crit_id-1)/3.0);' contents
      ) v
order by order_id;

这基本上只使用查询的字符串模板,然后为条件和 val 列插入适当的值组合。您甚至可以通过阅读 information_schema.columns 中的列名来摆脱 with 子句,但我认为基本思想在上面的版本中更加清晰。请注意,生成的代码在最后一列之后(在 from 子句之前)包含一个逗号太多。事后手动删除它比在生成器中更正它更容易。

准备交叉表

为了使用crosstab()功能,必须重新组织数据。您需要一个包含三列(row numbercriteriumvalue)的数据集。要将所有值放在一列中,您必须 unpivot 最后两列,同时更改标准名称。作为行号,您可以根据新条件对分区使用 rank() 函数。

select rank() over (partition by criterium order by crit_id), criterium, val
from (
    select crit_id, criterium || '_v1' criterium, val1 val
    from crit
    union
    select crit_id, criterium || '_v2' criterium, val2 val
    from crit
    ) sub
order by 1, 2

 rank | criterium | val 
------+-----------+-----
    1 | T01_v1    |   9
    1 | T01_v2    |   9
    1 | T02_v1    |   3
    1 | T02_v2    |   5
    1 | T03_v1    |   4
    1 | T03_v2    |   9
    2 | T01_v1    |   2
    2 | T01_v2    |   3
    2 | T02_v1    |   5
    2 | T02_v2    |   1
    2 | T03_v1    |   6
    2 | T03_v2    |   1
(12 rows)

此数据集可用于 crosstab():

create extension if not exists tablefunc;

select * from crosstab($ct$
    select rank() over (partition by criterium order by crit_id), criterium, val
    from (
        select crit_id, criterium || '_v1' criterium, val1 val
        from crit
        union
        select crit_id, criterium || '_v2' criterium, val2 val
        from crit
        ) sub
    order by 1, 2
    $ct$)
as ct (rank bigint, "T01_v1" int, "T01_v2" int, 
                    "T02_v1" int, "T02_v2" int, 
                    "T03_v1" int, "T03_v2" int);

 rank | T01_v1 | T01_v2 | T02_v1 | T02_v2 | T03_v1 | T03_v2
------+--------+--------+--------+--------+--------+--------
    1 |      9 |      9 |      3 |      5 |      4 |      9
    2 |      2 |      3 |      5 |      1 |      6 |      1
(2 rows)

备选方案

对于77个条件*7个参数,上面的查询可能比较麻烦。如果您可以接受稍微不同的数据呈现方式,问题就会变得容易得多。

select * from crosstab($ct$
    select 
        rank() over (partition by criterium order by crit_id),
        criterium,
        concat_ws(' | ', val1, val2) vals
    from crit
    order by 1, 2
    $ct$)
as ct (rank bigint, "T01" text, "T02" text, "T03" text);

 rank |  T01  |  T02  |  T03
------+-------+-------+-------
    1 | 9 | 9 | 3 | 5 | 4 | 9
    2 | 2 | 3 | 5 | 1 | 6 | 1
(2 rows)