SQL 可变线的枢轴
SQL pivot with variable lines
我必须将可变数量的行放入列中。我有类似的东西:
EMP EMP_ID DIV_ID ALLOCATION
Smith, Tom 3605 11300 20.00
Smith, Tom 13605 11310 80.00
Benetar, Pat 7460 11012 25.00
Benetar, Pat 7460 11015 75.00
Walkin, Chris 13892 11012 90.00
Walkin, Chris 13892 11015 10.00
Kent, Clark 12262 10015 50.00
Kent, Clark 12262 11210 25.00
Kent, Clark 12262 11220 25.00
我正在寻找的是这样的东西:
EMP EMP_ID DIV_ID_01 DIV_01_ALOC DIV_ID_02 DIV_02_ALOC DIV_ID_03 DIV_03_ALOC
Smith, Tom 3605 11300 20.00 11310 80.00
Benetar, Pat 13605 11012 25.00 11015 75.00
Walkin, Chris 13892 11012 90.00 11015 10.00
Kent, Clark 12262 11015 50.00 11210 25.00 11220 25.00
我想避免使用大量的 CASE 语句。我现在正在尝试使用枢轴,但在使用 headers.
时遇到了困难
更新:
经过几次尝试,我想出了下面的解决方案,它利用两个 PIVOT
函数和一个 GROUP BY
来匹配您的预期结果。
这是下面的代码。注意:这适用于 SQL Server 2005+
with testdata(Emp, EMP_ID, DIV_ID, ALLOCATION)
as
(
select 'Smith, Tom',3605, 11300,20.00
union all
select 'Smith, Tom',3605, 11310, 80.00
union all
select 'Benetar, Pat',7460, 11012,25.00
union all
select 'Benetar, Pat',7460, 11015,75.00
union all
select 'Walkin, Chris',13892, 11012, 90.00
union all
select 'Walkin, Chris', 13892, 11015, 10.00
union all
select 'Kent, Clark', 12262, 10015, 50.00
union all
select 'Kent, Clark', 12262, 11210, 25.00
union all
select 'Kent, Clark', 12262, 11220, 25.00
)
SELECT Emp
,EMP_ID
,MAX([Div1]) AS DIV_ID_01
,MAX([Alloc1]) AS DIV_01_ALOC
,MAX([Div2]) AS DIV_ID_02
,MAX([Alloc2]) AS DIV_02_ALOC
,MAX([Div3]) AS DIV_ID_03
,MAX([Alloc3]) AS DIV_03_ALOC
FROM (
SELECT *
,cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [emp_rnk]
,'Alloc' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Alloc_rnk]
,'Div' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Div_rnk]
FROM testdata td
) query
/* After both PIVOT functions are compplete, it still returns a single row for each EMP_ID.
So further aggregation is needed to 'flatten' the result. */
PIVOT (Max(Div_id) FOR [piv_Div_rnk] IN ([Div1],[Div2],[Div3])) AS pivot1
PIVOT (Max(Allocation) FOR [piv_Alloc_rnk] in([Alloc1],[Alloc2],[Alloc3])) AS pivot2
/* Since there is only one value in each of the columns created by the PIVOTS for each EMP_ID taking
the MAX() value and grouping by EMP and EMP_ID flattens the result down to the desired output. */
GROUP BY emp, emp_id
ORDER BY DIV_ID_01 DESC
仅供参考,如果您尝试在此处使用动态 PIVOT,您最终将需要一个类似如下的查询。
SELECT [EMP],
[EMP_ID],
MIN(DIV_ID_1) [DIV_ID_1],
SUM(DIV_1_ALOC) [DIV_1_ALOC],
MIN(DIV_ID_2) [DIV_ID_2],
SUM(DIV_2_ALOC) [DIV_2_ALOC],
MIN(DIV_ID_3) [DIV_ID_3],
SUM(DIV_3_ALOC) [DIV_3_ALOC]
FROM (SELECT [EMP],
[EMP_ID],
[DIV_ID],
[ALLOCATION],
CONCAT('DIV_ID_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) ID_RN,
CONCAT('DIV_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]),'_ALOC') ALLOC_RN
FROM EmpTable
) t
PIVOT (
MIN([DIV_ID])
FOR ID_RN IN ([DIV_ID_1],[DIV_ID_2],[DIV_ID_3]) ) p1
PIVOT (
SUM([ALLOCATION])
FOR ALLOC_RN IN ([DIV_1_ALOC],[DIV_2_ALOC],[DIV_3_ALOC]) ) p2
GROUP BY [EMP],
[EMP_ID]
由于双主元,您需要动态创建 SELECT 和 PIVOT 列。
另一方面,如果您使用 CASE 语句,您只需要动态创建 SELECT,因为该查询看起来像
SELECT [EMP],
[EMP_ID],
MIN(CASE WHEN RN = 1 THEN [DIV_ID] END) [DIV_ID_1],
SUM(CASE WHEN RN = 1 THEN [ALLOCATION] END) [DIV_1_ALOC],
MIN(CASE WHEN RN = 2 THEN [DIV_ID] END) [DIV_ID_2],
SUM(CASE WHEN RN = 2 THEN [ALLOCATION] END) [DIV_2_ALOC],
MIN(CASE WHEN RN = 3 THEN [DIV_ID] END) [DIV_ID_3],
SUM(CASE WHEN RN = 3 THEN [ALLOCATION] END) [DIV_3_ALOC]
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP],
[EMP_ID]
您的动态声明应该类似于
DECLARE @CaseSelect VARCHAR(MAX)
SELECT @CaseSelect = COALESCE(@CaseSelect + ',','')
+ 'MIN(CASE WHEN RN = ' + RN + ' THEN [DIV_ID] END) [DIV_ID_' + RN + '],'
+ 'SUM(CASE WHEN RN = ' + RN + ' THEN [ALLOCATION] END) [DIV_' + RN + '_ALOC]'
FROM (
SELECT DISTINCT CONVERT(VARCHAR(2),DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) RN
FROM EmpTable
) t
ORDER BY RN
DECLARE @SQL VARCHAR(MAX)
SET @SQL = '
SELECT [EMP],
[EMP_ID], '
+ @CaseSelect + '
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP], [EMP_ID]
'
EXEC(@SQL)
将 EmpTable
替换为您实际的 table 课程名称。
我必须将可变数量的行放入列中。我有类似的东西:
EMP EMP_ID DIV_ID ALLOCATION
Smith, Tom 3605 11300 20.00
Smith, Tom 13605 11310 80.00
Benetar, Pat 7460 11012 25.00
Benetar, Pat 7460 11015 75.00
Walkin, Chris 13892 11012 90.00
Walkin, Chris 13892 11015 10.00
Kent, Clark 12262 10015 50.00
Kent, Clark 12262 11210 25.00
Kent, Clark 12262 11220 25.00
我正在寻找的是这样的东西:
EMP EMP_ID DIV_ID_01 DIV_01_ALOC DIV_ID_02 DIV_02_ALOC DIV_ID_03 DIV_03_ALOC
Smith, Tom 3605 11300 20.00 11310 80.00
Benetar, Pat 13605 11012 25.00 11015 75.00
Walkin, Chris 13892 11012 90.00 11015 10.00
Kent, Clark 12262 11015 50.00 11210 25.00 11220 25.00
我想避免使用大量的 CASE 语句。我现在正在尝试使用枢轴,但在使用 headers.
时遇到了困难更新:
经过几次尝试,我想出了下面的解决方案,它利用两个 PIVOT
函数和一个 GROUP BY
来匹配您的预期结果。
这是下面的代码。注意:这适用于 SQL Server 2005+
with testdata(Emp, EMP_ID, DIV_ID, ALLOCATION)
as
(
select 'Smith, Tom',3605, 11300,20.00
union all
select 'Smith, Tom',3605, 11310, 80.00
union all
select 'Benetar, Pat',7460, 11012,25.00
union all
select 'Benetar, Pat',7460, 11015,75.00
union all
select 'Walkin, Chris',13892, 11012, 90.00
union all
select 'Walkin, Chris', 13892, 11015, 10.00
union all
select 'Kent, Clark', 12262, 10015, 50.00
union all
select 'Kent, Clark', 12262, 11210, 25.00
union all
select 'Kent, Clark', 12262, 11220, 25.00
)
SELECT Emp
,EMP_ID
,MAX([Div1]) AS DIV_ID_01
,MAX([Alloc1]) AS DIV_01_ALOC
,MAX([Div2]) AS DIV_ID_02
,MAX([Alloc2]) AS DIV_02_ALOC
,MAX([Div3]) AS DIV_ID_03
,MAX([Alloc3]) AS DIV_03_ALOC
FROM (
SELECT *
,cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [emp_rnk]
,'Alloc' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Alloc_rnk]
,'Div' + cast(dense_rank() OVER (PARTITION BY emp_id
ORDER BY div_id asc) AS nvarchar) AS [piv_Div_rnk]
FROM testdata td
) query
/* After both PIVOT functions are compplete, it still returns a single row for each EMP_ID.
So further aggregation is needed to 'flatten' the result. */
PIVOT (Max(Div_id) FOR [piv_Div_rnk] IN ([Div1],[Div2],[Div3])) AS pivot1
PIVOT (Max(Allocation) FOR [piv_Alloc_rnk] in([Alloc1],[Alloc2],[Alloc3])) AS pivot2
/* Since there is only one value in each of the columns created by the PIVOTS for each EMP_ID taking
the MAX() value and grouping by EMP and EMP_ID flattens the result down to the desired output. */
GROUP BY emp, emp_id
ORDER BY DIV_ID_01 DESC
仅供参考,如果您尝试在此处使用动态 PIVOT,您最终将需要一个类似如下的查询。
SELECT [EMP],
[EMP_ID],
MIN(DIV_ID_1) [DIV_ID_1],
SUM(DIV_1_ALOC) [DIV_1_ALOC],
MIN(DIV_ID_2) [DIV_ID_2],
SUM(DIV_2_ALOC) [DIV_2_ALOC],
MIN(DIV_ID_3) [DIV_ID_3],
SUM(DIV_3_ALOC) [DIV_3_ALOC]
FROM (SELECT [EMP],
[EMP_ID],
[DIV_ID],
[ALLOCATION],
CONCAT('DIV_ID_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) ID_RN,
CONCAT('DIV_',DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]),'_ALOC') ALLOC_RN
FROM EmpTable
) t
PIVOT (
MIN([DIV_ID])
FOR ID_RN IN ([DIV_ID_1],[DIV_ID_2],[DIV_ID_3]) ) p1
PIVOT (
SUM([ALLOCATION])
FOR ALLOC_RN IN ([DIV_1_ALOC],[DIV_2_ALOC],[DIV_3_ALOC]) ) p2
GROUP BY [EMP],
[EMP_ID]
由于双主元,您需要动态创建 SELECT 和 PIVOT 列。
另一方面,如果您使用 CASE 语句,您只需要动态创建 SELECT,因为该查询看起来像
SELECT [EMP],
[EMP_ID],
MIN(CASE WHEN RN = 1 THEN [DIV_ID] END) [DIV_ID_1],
SUM(CASE WHEN RN = 1 THEN [ALLOCATION] END) [DIV_1_ALOC],
MIN(CASE WHEN RN = 2 THEN [DIV_ID] END) [DIV_ID_2],
SUM(CASE WHEN RN = 2 THEN [ALLOCATION] END) [DIV_2_ALOC],
MIN(CASE WHEN RN = 3 THEN [DIV_ID] END) [DIV_ID_3],
SUM(CASE WHEN RN = 3 THEN [ALLOCATION] END) [DIV_3_ALOC]
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP],
[EMP_ID]
您的动态声明应该类似于
DECLARE @CaseSelect VARCHAR(MAX)
SELECT @CaseSelect = COALESCE(@CaseSelect + ',','')
+ 'MIN(CASE WHEN RN = ' + RN + ' THEN [DIV_ID] END) [DIV_ID_' + RN + '],'
+ 'SUM(CASE WHEN RN = ' + RN + ' THEN [ALLOCATION] END) [DIV_' + RN + '_ALOC]'
FROM (
SELECT DISTINCT CONVERT(VARCHAR(2),DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID])) RN
FROM EmpTable
) t
ORDER BY RN
DECLARE @SQL VARCHAR(MAX)
SET @SQL = '
SELECT [EMP],
[EMP_ID], '
+ @CaseSelect + '
FROM (
SELECT *,
DENSE_RANK () OVER (PARTITION BY [EMP] ORDER BY [DIV_ID]) RN
FROM EmpTable
) t
GROUP BY [EMP], [EMP_ID]
'
EXEC(@SQL)
将 EmpTable
替换为您实际的 table 课程名称。