我有下表:
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个标准和7种不同的值。所以我必须写539 case
个语句。什么是动态创建它们的最佳方式?
我正在使用PostgreSql 9.4
答案 0 :(得分:3)
为了使用crosstab()
功能,必须重新组织数据。您需要一个包含三列(row number
,criterium
,value
)的数据集。要将所有值放在一列中,您必须取消忽略最后两列,同时更改条件名称。作为行号,您可以按新条件在分区上使用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)
答案 1 :(得分:0)
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
答案 2 :(得分:0)
我同意迈克尔的评论,这个要求看起来有点奇怪,但如果你真的需要这样,那么你的解决方案是正确的。它只需要一些额外的代码(以及在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);
其工作原理如下。要将您发布的结果汇总到您想要的结果中,第一个有用的观察结果是所需的结果比初步结果的行少。因此,必须进行某种分组,关键问题是:"分组标准是什么?"在这种情况下,它是相当不明显的:它的标准ID(减1,以0开始计数)除以3,并被截断。这三个来自不同标准的数量。解决了这个难题后,很容易看出,对于聚合到同一结果行的输入行中,每列只有一个非空值。这意味着聚合函数的选择并不那么重要,因为只需要返回唯一的非空值。我在代码片段中使用了总和,但您也可以使用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子句之前)之后直接包含一个逗号。之后手动删除它比在生成器中更正它更容易。