如何在 SQL Server 2008 中转换数据以获得所需的结果?
How to pivot data in SQL Server 2008 to get the desired results?
在过去得到这个主题的非常好的答案之后 (here and here)。似乎我仍然无法绕过这个枢轴的东西(我也很长时间没有使用它)。
所以希望有人(再次)能够告诉我如何将数据转换为所需的格式:
给出的数据:
ID | Label | Occurences | RangeBegin | RangeEnd | Unit
---+-------+------------+------------+----------+--------
1 | One | 0 | -1000 | 0 | m
1 | One | 5 | 0 | 10 | m
1 | One | 8 | 10 | 20 | m
1 | One | 6 | 20 | 30 | m
1 | One | 15 | 30 | 40 | m
1 | One | 0 | 40 | 1000 | m
2 | One | 0 | -1000 | 0 | m
2 | One | 2 | 0 | 10 | m
2 | One | 13 | 10 | 20 | m
2 | One | 27 | 20 | 30 | m
2 | One | 5 | 30 | 40 | m
2 | One | 0 | 40 | 1000 | m
1 | Two | 0 | -1000 | 0 | kg
1 | Two | 4 | 0 | 2 | kg
1 | Two | 6 | 2 | 4 | kg
1 | Two | 1 | 4 | 6 | kg
1 | Two | 0 | 6 | 1000 | kg
2 | Two | 0 | -1000 | 0 | kg
2 | Two | 8 | 0 | 2 | kg
2 | Two | 1 | 2 | 4 | kg
2 | Two | 3 | 4 | 6 | kg
2 | Two | 0 | 6 | 1000 | kg
想要的结果:
ID | One | OneRangeBegin | OneRangeEnd | OneUnit | Two | TwoRangeBegin | TwoRangeEnd | TwoUnit
---+-----+---------------+-------------+---------+------+---------------+-------------+----------
1 | 0 | -1000 | 0 | m | 0 | -1000 | 0 | kg
1 | 5 | 0 | 10 | m | 4 | 0 | 2 | kg
1 | 8 | 10 | 20 | m | 6 | 2 | 4 | kg
1 | 6 | 20 | 30 | m | 1 | 4 | 6 | kg
1 | 15 | 30 | 40 | m | 0 | 6 | 1000 | kg
1 | 0 | 40 | 1000 | m | null | null | null | null
2 | 0 | -1000 | 0 | m | 0 | -1000 | 0 | kg
2 | 2 | 0 | 10 | m | 8 | 0 | 2 | kg
2 | 13 | 10 | 20 | m | 1 | 2 | 4 | kg
2 | 27 | 20 | 30 | m | 3 | 4 | 6 | kg
2 | 5 | 30 | 40 | m | 0 | 6 | 1000 | kg
2 | 0 | 40 | 1000 | m | null | null | null | null
同时为了让工作更简单一些,我把上面的数据放在SqlFiddle。
我也总是难以理解枢轴。出于这个原因,我倾向于使用聚合 CASE 方法而不是 PIVOT,因为我发现它更容易推理(而且更灵活)。基本上你需要把你的问题分解成多个步骤。
- 确定数据的分组方式 - 例如,按 ID、RangeBegin、RangeEnd
- 确定所有列的内容以及它们的来源
- 为每个单独的列编写聚合 CASE 语句 - 注意:您必须确保每个输出都是明确的,否则您将丢失结果
聚合 CASE 枢轴的总体布局如下:
SELECT
grouped columns
,MAX(CASE WHEN condition THEN column END) as ColumnName
,...
FROM
Table
GROUP BY grouped columns
关键是上面的条件会识别每一列的来源。因此,例如,当 Label
列为 One
时,OneUnit
列显然来自 Unit
列,因此将是:
MAX(CASE WHEN Label='One' THEN Unit END) as OneUnit
这很容易应用于您的特定示例,除了没有明显的方法将行从一到二关联在一起,因为您将不同的范围组合在一起。在构建实际的数据透视查询之前,您需要确定一个明确的规则将数据透视表中的行组合在一起,这应该非常简单(这会生成额外的行,因为一组和两组之间的范围不同):
select
D.ID, D.RangeBegin, D.RangeEnd
,MAX(CASE WHEN Label='One' THEN D.Occurences END) as One
,MAX(CASE WHEN Label='One' THEN D.RangeBegin END) as OneRangeBegin
,MAX(CASE WHEN Label='One' THEN D.RangeEnd END) as OneRangeEnd
,MAX(CASE WHEN Label='One' THEN D.Unit END) as OneUnit
,MAX(CASE WHEN Label='Two' THEN D.Occurences END) as Two
,MAX(CASE WHEN Label='Two' THEN D.RangeBegin END) as TwoRangeBegin
,MAX(CASE WHEN Label='Two' THEN D.RangeEnd END) as vRangeEnd
,MAX(CASE WHEN Label='Two' THEN D.Unit END) as TwoUnit
from
AvailableData D
group by
D.ID, D.RangeBegin, D.RangeEnd
我认为可以通过这样做来实现:
- 根据标签拆分 Table
- 通过对 ID 进行分区并根据 RangeBegin 对其进行排列,对每个 table 进行排序。
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin)
- 完全加入两个 TableID 和等级
SELECT
A.ID AS ID
,A.Occurences AS One
,A.RangeBegin AS OneRangeBegin
,A.RangeEnd AS OneRangeEnd
,A.Unit AS OneUnit
,B.Occurences AS Two
,B.RangeBegin AS TwoRangeBegin
,B.RangeEnd AS TwoRangeEnd
,B.Unit AS TwoUnit
FROM
(select
*, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin) Rank
from
AvailableData
wHere Label = 'One') A
FULL JOIN
(select
*, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin) Rank
from
AvailableData
Where Label = 'Two' ) B
ON A.ID = B.ID
AND A.Rank = B.Rank
ORDER BY ISNULL(A.ID, B.ID), ISNULL(A.Rank, B.Rank)
在过去得到这个主题的非常好的答案之后 (here and here)。似乎我仍然无法绕过这个枢轴的东西(我也很长时间没有使用它)。
所以希望有人(再次)能够告诉我如何将数据转换为所需的格式:
给出的数据:
ID | Label | Occurences | RangeBegin | RangeEnd | Unit
---+-------+------------+------------+----------+--------
1 | One | 0 | -1000 | 0 | m
1 | One | 5 | 0 | 10 | m
1 | One | 8 | 10 | 20 | m
1 | One | 6 | 20 | 30 | m
1 | One | 15 | 30 | 40 | m
1 | One | 0 | 40 | 1000 | m
2 | One | 0 | -1000 | 0 | m
2 | One | 2 | 0 | 10 | m
2 | One | 13 | 10 | 20 | m
2 | One | 27 | 20 | 30 | m
2 | One | 5 | 30 | 40 | m
2 | One | 0 | 40 | 1000 | m
1 | Two | 0 | -1000 | 0 | kg
1 | Two | 4 | 0 | 2 | kg
1 | Two | 6 | 2 | 4 | kg
1 | Two | 1 | 4 | 6 | kg
1 | Two | 0 | 6 | 1000 | kg
2 | Two | 0 | -1000 | 0 | kg
2 | Two | 8 | 0 | 2 | kg
2 | Two | 1 | 2 | 4 | kg
2 | Two | 3 | 4 | 6 | kg
2 | Two | 0 | 6 | 1000 | kg
想要的结果:
ID | One | OneRangeBegin | OneRangeEnd | OneUnit | Two | TwoRangeBegin | TwoRangeEnd | TwoUnit
---+-----+---------------+-------------+---------+------+---------------+-------------+----------
1 | 0 | -1000 | 0 | m | 0 | -1000 | 0 | kg
1 | 5 | 0 | 10 | m | 4 | 0 | 2 | kg
1 | 8 | 10 | 20 | m | 6 | 2 | 4 | kg
1 | 6 | 20 | 30 | m | 1 | 4 | 6 | kg
1 | 15 | 30 | 40 | m | 0 | 6 | 1000 | kg
1 | 0 | 40 | 1000 | m | null | null | null | null
2 | 0 | -1000 | 0 | m | 0 | -1000 | 0 | kg
2 | 2 | 0 | 10 | m | 8 | 0 | 2 | kg
2 | 13 | 10 | 20 | m | 1 | 2 | 4 | kg
2 | 27 | 20 | 30 | m | 3 | 4 | 6 | kg
2 | 5 | 30 | 40 | m | 0 | 6 | 1000 | kg
2 | 0 | 40 | 1000 | m | null | null | null | null
同时为了让工作更简单一些,我把上面的数据放在SqlFiddle。
我也总是难以理解枢轴。出于这个原因,我倾向于使用聚合 CASE 方法而不是 PIVOT,因为我发现它更容易推理(而且更灵活)。基本上你需要把你的问题分解成多个步骤。
- 确定数据的分组方式 - 例如,按 ID、RangeBegin、RangeEnd
- 确定所有列的内容以及它们的来源
- 为每个单独的列编写聚合 CASE 语句 - 注意:您必须确保每个输出都是明确的,否则您将丢失结果
聚合 CASE 枢轴的总体布局如下:
SELECT
grouped columns
,MAX(CASE WHEN condition THEN column END) as ColumnName
,...
FROM
Table
GROUP BY grouped columns
关键是上面的条件会识别每一列的来源。因此,例如,当 Label
列为 One
时,OneUnit
列显然来自 Unit
列,因此将是:
MAX(CASE WHEN Label='One' THEN Unit END) as OneUnit
这很容易应用于您的特定示例,除了没有明显的方法将行从一到二关联在一起,因为您将不同的范围组合在一起。在构建实际的数据透视查询之前,您需要确定一个明确的规则将数据透视表中的行组合在一起,这应该非常简单(这会生成额外的行,因为一组和两组之间的范围不同):
select
D.ID, D.RangeBegin, D.RangeEnd
,MAX(CASE WHEN Label='One' THEN D.Occurences END) as One
,MAX(CASE WHEN Label='One' THEN D.RangeBegin END) as OneRangeBegin
,MAX(CASE WHEN Label='One' THEN D.RangeEnd END) as OneRangeEnd
,MAX(CASE WHEN Label='One' THEN D.Unit END) as OneUnit
,MAX(CASE WHEN Label='Two' THEN D.Occurences END) as Two
,MAX(CASE WHEN Label='Two' THEN D.RangeBegin END) as TwoRangeBegin
,MAX(CASE WHEN Label='Two' THEN D.RangeEnd END) as vRangeEnd
,MAX(CASE WHEN Label='Two' THEN D.Unit END) as TwoUnit
from
AvailableData D
group by
D.ID, D.RangeBegin, D.RangeEnd
我认为可以通过这样做来实现:
- 根据标签拆分 Table
- 通过对 ID 进行分区并根据 RangeBegin 对其进行排列,对每个 table 进行排序。
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin)
- 完全加入两个 TableID 和等级
SELECT
A.ID AS ID
,A.Occurences AS One
,A.RangeBegin AS OneRangeBegin
,A.RangeEnd AS OneRangeEnd
,A.Unit AS OneUnit
,B.Occurences AS Two
,B.RangeBegin AS TwoRangeBegin
,B.RangeEnd AS TwoRangeEnd
,B.Unit AS TwoUnit
FROM
(select
*, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin) Rank
from
AvailableData
wHere Label = 'One') A
FULL JOIN
(select
*, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY RangeBegin) Rank
from
AvailableData
Where Label = 'Two' ) B
ON A.ID = B.ID
AND A.Rank = B.Rank
ORDER BY ISNULL(A.ID, B.ID), ISNULL(A.Rank, B.Rank)