如何使用列名旋转或 '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 number
、criterium
、value
)的数据集。要将所有值放在一列中,您必须 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)
我有以下 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 number
、criterium
、value
)的数据集。要将所有值放在一列中,您必须 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)