Sql 如何使用列名透视或“合并”行?

Sql 如何使用列名透视或“合并”行?,sql,postgresql,pivot,Sql,Postgresql,Pivot,我有下表: crit_id | criterium | val1 | val2 ----------+------------+-------+-------- 1 | T01 | 9 | 9 2 | T02 | 3 | 5 3 | T03 | 4 | 9 4 | T01 | 2 | 3 5 | T

我有下表:

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的“叉积”。因此,结果必须如下所示:

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个标准,每个标准有7种不同的值。所以我要写539个案例陈述。动态创建它们的最佳方法是什么


我正在使用PostgreSql 9.4,我同意Michael的意见,即此要求看起来有点奇怪,但如果您真的需要这样做,那么您的解决方案是正确的。它只需要一点额外的代码和一些小的修正,凡val_1和val_2混淆的地方:

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
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);
其工作原理如下。要将您发布的结果聚合到您想要的结果中,第一个有用的观察结果是所需结果的行数少于您最初的行数。所以有必要进行某种分组,关键问题是:分组标准是什么?在本例中,这是相当不明显的:标准ID减去1,开始计数时0除以3,然后被截断。这三个标准来自不同的数量。解决了这个难题后,很容易看出,对于聚合到同一结果行中的输入行,每列只有一个非空值。这意味着聚合函数的选择并不重要,因为它只需要返回唯一的非空值。我在代码段中使用了sum,但您也可以使用min或max

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

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子句之前的最后一列后面包含了过多的逗号。事后手动删除比在生成器中更正更容易。

准备交叉表 为了使用函数,必须重新组织数据。您需要一个具有三列的数据集—行号、准则和值。要在一列中包含所有值,必须取消最后两列的IVOT,同时更改条件的名称。作为行号,您可以根据新的条件在分区上使用秩函数

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)
此数据集可用于交叉表:

替代解决方案 对于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)    

这是一个非常奇怪的要求。是否有什么原因使您必须以这种特定格式输出数据?如果您只需从crit_表中运行select criterium、val1、val2,它将准确地输出您所需的数据,而不会出现令人讨厌的可变几何表视图问题。不幸的是,我必须以这种格式导出数据。val1 | val2列名是否与val7类似?@wingᴇ数据处理ᴀNᴛʜᴇʀ,不,val1到val7只是保持简单的占位符。真正的列名使用自然语言,如:number_min、number_max、grade等。每行是否有一套完整的标准,或者是否有空白?crit_id真的没有漏洞吗?你确定这个解决方案在PostgreSQL中有效吗?让我们看看问卷会说什么,但它是纯SQL答案,与PostgreSQL无关。如果是这样,这里的标记有什么用?OP为PostgreSQL标记了他的问题,而你的解决方案在PostgreSQL中不起作用!DECLARE@Table1不是纯SQL——它是T-SQL。此外,[T01]或@table1在纯SQL@中是无效标识符,[or]在SQL标识符中是不允许的,并且纯SQL中的串联运算符是| | not,+pivot在postgresql中也不起作用。这不仅仅是一些语法上的甜点。你的解决方案提供了与我的方法完全相同的结果。我非常希望如此!OP已经定义了结果,所以我们应该得到相同的结果。解决方案也分享了一些想法,至少就我所知,不必花费太多时间来解码T-SQL,这对我来说有点难理解。这是一个相当优雅的解决方案!
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)