来自多列的动态/条件枚举
Dynamic / Conditional Enumeration From Multiple Columns
我正在尝试找出将多列枚举成两行的最佳方法。例如,下面的数据包含公司中每个职位的员工总数,按全职等效 (FTE) 状态细分。
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
| Position | PositionSlot | PositionSlotDescrpition | PD | P5 | P6 | P7 | P8 | P9 | FT |
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
| 1-400400-0680 | NULL | NULL | 7 | 1 | 2 | NULL | 1 | 18 | NULL |
| 1-400400-0041 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 1 |
| 1-400400-0660 | NULL | NULL | NULL | NULL | NULL | NULL | 1 | 6 | NULL |
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
所以对于 Position 1-400400-0680
,有 29 名员工如上所列。
我需要枚举这些聚合,以针对 FTE 突破为每个 Position
创建一个 顺序 PositionSlot
。
- 每个
Position
的总行数应等于 PD, P5, P6, P7, P8, P9, and FT
列的总和。
PositionsSlotDescription
应该是原来的列名PD, P5, P6, P7, P8, P9, and FT
PositionSlot
应该是这些行的顺序编号。 插槽的顺序无关紧要。 因此 PositionSlot = 1
可以属于任何 PositionsSlotDescription
PositionsSlotDescription
应该重复/
根据原始聚合中的数字进行复制。
例如 Position 1-400400-0660
有 1 个代表 P8
,6 个代表 P9
。因此,应该有 1 行 PositionSlotDescription
为 P8
和 6 行 PositionSlotDescription
为 P9
,PositionSlots
应该为 1-7
预期结果
+---------------+--------------+-------------------------+
| Position | PositionSlot | PositionSlotDescrpition |
+---------------+--------------+-------------------------+
| 1-400400-0041 | 1 | FT |
| 1-400400-0660 | 1 | P8 |
| 1-400400-0660 | 2 | P9 |
| 1-400400-0660 | 3 | P9 |
| 1-400400-0660 | 4 | P9 |
| 1-400400-0660 | 5 | P9 |
| 1-400400-0660 | 6 | P9 |
| 1-400400-0660 | 7 | P9 |
| 1-400400-0680 | 1 | P5 |
| 1-400400-0680 | 2 | P6 |
| 1-400400-0680 | 3 | P6 |
| 1-400400-0680 | 4 | P8 |
| 1-400400-0680 | 5 | P9 |
| 1-400400-0680 | 6 | P9 |
| 1-400400-0680 | 7 | P9 |
| 1-400400-0680 | 8 | P9 |
| 1-400400-0680 | 9 | P9 |
| 1-400400-0680 | 10 | P9 |
| 1-400400-0680 | 11 | P9 |
| 1-400400-0680 | 12 | P9 |
| 1-400400-0680 | 13 | P9 |
| 1-400400-0680 | 14 | P9 |
| 1-400400-0680 | 15 | P9 |
| 1-400400-0680 | 16 | P9 |
| 1-400400-0680 | 17 | P9 |
| 1-400400-0680 | 18 | P9 |
| 1-400400-0680 | 19 | P9 |
| 1-400400-0680 | 20 | P9 |
| 1-400400-0680 | 21 | P9 |
| 1-400400-0680 | 22 | P9 |
| 1-400400-0680 | 23 | PD |
| 1-400400-0680 | 24 | PD |
| 1-400400-0680 | 25 | PD |
| 1-400400-0680 | 26 | PD |
| 1-400400-0680 | 27 | PD |
| 1-400400-0680 | 28 | PD |
| 1-400400-0680 | 29 | PD |
+---------------+--------------+-------------------------+
测试脚本
declare @table table ( Position varchar(64) --UniqueIdentifier
,PositionSlot int
,PositionSlotDescrpition varchar(64)
,PD varchar(16)
,P5 varchar(16)
,P6 varchar(16)
,P7 varchar(16)
,P8 varchar(16)
,P9 varchar(16)
,FT varchar(16))
insert into @table
values
('1-400400-0680',NULL,NULL,7,1,2,NULL,1,18,NULL),
('1-400400-0041',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1),
('1-400400-0660',NULL,NULL,NULL,NULL,NULL,NULL,1,6,NULL)
declare @expectedResults table( Position varchar(64)
,PositionSlot int
,PositionSlotDescrpition varchar(64))
insert into @expectedResults
values
('1-400400-0041',1,'FT'),
('1-400400-0660',1,'P8'),
('1-400400-0660',2,'P9'),
('1-400400-0660',3,'P9'),
('1-400400-0660',4,'P9'),
('1-400400-0660',5,'P9'),
('1-400400-0660',6,'P9'),
('1-400400-0660',7,'P9'),
('1-400400-0680',1,'P5'),
('1-400400-0680',2,'P6'),
('1-400400-0680',3,'P6'),
('1-400400-0680',4,'P8'),
('1-400400-0680',5,'P9'),
('1-400400-0680',6,'P9'),
('1-400400-0680',7,'P9'),
('1-400400-0680',8,'P9'),
('1-400400-0680',9,'P9'),
('1-400400-0680',10,'P9'),
('1-400400-0680',11,'P9'),
('1-400400-0680',12,'P9'),
('1-400400-0680',13,'P9'),
('1-400400-0680',14,'P9'),
('1-400400-0680',15,'P9'),
('1-400400-0680',16,'P9'),
('1-400400-0680',17,'P9'),
('1-400400-0680',18,'P9'),
('1-400400-0680',19,'P9'),
('1-400400-0680',20,'P9'),
('1-400400-0680',21,'P9'),
('1-400400-0680',22,'P9'),
('1-400400-0680',23,'PD'),
('1-400400-0680',24,'PD'),
('1-400400-0680',25,'PD'),
('1-400400-0680',26,'PD'),
('1-400400-0680',27,'PD'),
('1-400400-0680',28,'PD'),
('1-400400-0680',29,'PD')
使用临时数字 table 和 cross apply(values ...)
来逆透视您的数据:
;with numbers as (
select top (32) --<-- 32 works for the example, increase for larger sets
i=row_number() over(order by (select 1))
from master..spt_values
order by i
)
select
t.Position
, PositionSlot=row_number() over (
partition by t.Position
order by v.PositionSlotDescription, n.i
)
, v.PositionSlotDescription
from @table t
cross apply (values
('PD',PD),('P5',P5) ,('P6',P6) ,('P7',P7) ,('P8',P8) ,('P9',P9) ,('FT',FT)
) v (PositionSlotDescription, Amount)
inner join numbers n
on n.i <= v.amount
where v.Amount is not null
order by t.Position, v.PositionSlotDescription
rextester 演示:http://rextester.com/AMKCR70455
returns:
+---------------+--------------+-------------------------+
| Position | PositionSlot | PositionSlotDescription |
+---------------+--------------+-------------------------+
| 1-400400-0041 | 1 | FT |
| 1-400400-0660 | 1 | P8 |
| 1-400400-0660 | 2 | P9 |
| 1-400400-0660 | 3 | P9 |
| 1-400400-0660 | 4 | P9 |
| 1-400400-0660 | 5 | P9 |
| 1-400400-0660 | 6 | P9 |
| 1-400400-0660 | 7 | P9 |
| 1-400400-0680 | 1 | P5 |
| 1-400400-0680 | 2 | P6 |
| 1-400400-0680 | 3 | P6 |
| 1-400400-0680 | 4 | P8 |
| 1-400400-0680 | 5 | P9 |
| 1-400400-0680 | 6 | P9 |
| 1-400400-0680 | 7 | P9 |
| 1-400400-0680 | 8 | P9 |
| 1-400400-0680 | 9 | P9 |
| 1-400400-0680 | 10 | P9 |
| 1-400400-0680 | 11 | P9 |
| 1-400400-0680 | 12 | P9 |
| 1-400400-0680 | 13 | P9 |
| 1-400400-0680 | 14 | P9 |
| 1-400400-0680 | 15 | P9 |
| 1-400400-0680 | 16 | P9 |
| 1-400400-0680 | 17 | P9 |
| 1-400400-0680 | 18 | P9 |
| 1-400400-0680 | 19 | P9 |
| 1-400400-0680 | 20 | P9 |
| 1-400400-0680 | 21 | P9 |
| 1-400400-0680 | 22 | P9 |
| 1-400400-0680 | 23 | PD |
| 1-400400-0680 | 24 | PD |
| 1-400400-0680 | 25 | PD |
| 1-400400-0680 | 26 | PD |
| 1-400400-0680 | 27 | PD |
| 1-400400-0680 | 28 | PD |
| 1-400400-0680 | 29 | PD |
+---------------+--------------+-------------------------+
参考:
- Generate a set or sequence without loops - 1 - Aaron Bertrand
- Generate a set or sequence without loops - 2 - Aaron Bertrand
- Generate a set or sequence without loops - 3 - Aaron Bertrand
- The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
对于更大的集合,您可以将 cross join from master..spt_values
添加到上面的 number
cte,或者将 numbers
cte 替换为这个备用堆叠 cte:
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, numbers as (
select top(50000)
i=row_number() over (order by (select 1))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
)
rextester 演示:http://rextester.com/OZZHR43374
具有 UNPIVOT
和递归 CTE
的选项
--Unpivot results
if object_id('tempdb..#unpivot') is not null drop table #unpivot
select
u.Position
,u.Code
,u.Descr
into #unpivot
from
@table t
unpivot
(
code
for Descr in (PD, P5, P6, P7, P8, P9, FT)
) u
--Recursive CTE
;with cte as(
select
t.Position
,Code = 1
,t.Descr
from #unpivot t
union all
select
t2.Position
,cte.Code + 1
,t2.Descr
from #unpivot t2
inner join cte
on cte.Position = t2.Position
and cte.Descr = t2.Descr
where t2.Code > cte.Code)
--Results
select
Position
--,Code
,PositionSlotCode = row_number() over (partition by Position order by Descr)
,Descr
from
cte
order by
Position
,Descr
,Code
我正在尝试找出将多列枚举成两行的最佳方法。例如,下面的数据包含公司中每个职位的员工总数,按全职等效 (FTE) 状态细分。
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
| Position | PositionSlot | PositionSlotDescrpition | PD | P5 | P6 | P7 | P8 | P9 | FT |
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
| 1-400400-0680 | NULL | NULL | 7 | 1 | 2 | NULL | 1 | 18 | NULL |
| 1-400400-0041 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 1 |
| 1-400400-0660 | NULL | NULL | NULL | NULL | NULL | NULL | 1 | 6 | NULL |
+---------------+--------------+-------------------------+------+------+------+------+------+------+------+
所以对于 Position 1-400400-0680
,有 29 名员工如上所列。
我需要枚举这些聚合,以针对 FTE 突破为每个 Position
创建一个 顺序 PositionSlot
。
- 每个
Position
的总行数应等于PD, P5, P6, P7, P8, P9, and FT
列的总和。 PositionsSlotDescription
应该是原来的列名PD, P5, P6, P7, P8, P9, and FT
PositionSlot
应该是这些行的顺序编号。 插槽的顺序无关紧要。 因此PositionSlot = 1
可以属于任何PositionsSlotDescription
PositionsSlotDescription
应该重复/ 根据原始聚合中的数字进行复制。
例如 Position 1-400400-0660
有 1 个代表 P8
,6 个代表 P9
。因此,应该有 1 行 PositionSlotDescription
为 P8
和 6 行 PositionSlotDescription
为 P9
,PositionSlots
应该为 1-7
预期结果
+---------------+--------------+-------------------------+
| Position | PositionSlot | PositionSlotDescrpition |
+---------------+--------------+-------------------------+
| 1-400400-0041 | 1 | FT |
| 1-400400-0660 | 1 | P8 |
| 1-400400-0660 | 2 | P9 |
| 1-400400-0660 | 3 | P9 |
| 1-400400-0660 | 4 | P9 |
| 1-400400-0660 | 5 | P9 |
| 1-400400-0660 | 6 | P9 |
| 1-400400-0660 | 7 | P9 |
| 1-400400-0680 | 1 | P5 |
| 1-400400-0680 | 2 | P6 |
| 1-400400-0680 | 3 | P6 |
| 1-400400-0680 | 4 | P8 |
| 1-400400-0680 | 5 | P9 |
| 1-400400-0680 | 6 | P9 |
| 1-400400-0680 | 7 | P9 |
| 1-400400-0680 | 8 | P9 |
| 1-400400-0680 | 9 | P9 |
| 1-400400-0680 | 10 | P9 |
| 1-400400-0680 | 11 | P9 |
| 1-400400-0680 | 12 | P9 |
| 1-400400-0680 | 13 | P9 |
| 1-400400-0680 | 14 | P9 |
| 1-400400-0680 | 15 | P9 |
| 1-400400-0680 | 16 | P9 |
| 1-400400-0680 | 17 | P9 |
| 1-400400-0680 | 18 | P9 |
| 1-400400-0680 | 19 | P9 |
| 1-400400-0680 | 20 | P9 |
| 1-400400-0680 | 21 | P9 |
| 1-400400-0680 | 22 | P9 |
| 1-400400-0680 | 23 | PD |
| 1-400400-0680 | 24 | PD |
| 1-400400-0680 | 25 | PD |
| 1-400400-0680 | 26 | PD |
| 1-400400-0680 | 27 | PD |
| 1-400400-0680 | 28 | PD |
| 1-400400-0680 | 29 | PD |
+---------------+--------------+-------------------------+
测试脚本
declare @table table ( Position varchar(64) --UniqueIdentifier
,PositionSlot int
,PositionSlotDescrpition varchar(64)
,PD varchar(16)
,P5 varchar(16)
,P6 varchar(16)
,P7 varchar(16)
,P8 varchar(16)
,P9 varchar(16)
,FT varchar(16))
insert into @table
values
('1-400400-0680',NULL,NULL,7,1,2,NULL,1,18,NULL),
('1-400400-0041',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1),
('1-400400-0660',NULL,NULL,NULL,NULL,NULL,NULL,1,6,NULL)
declare @expectedResults table( Position varchar(64)
,PositionSlot int
,PositionSlotDescrpition varchar(64))
insert into @expectedResults
values
('1-400400-0041',1,'FT'),
('1-400400-0660',1,'P8'),
('1-400400-0660',2,'P9'),
('1-400400-0660',3,'P9'),
('1-400400-0660',4,'P9'),
('1-400400-0660',5,'P9'),
('1-400400-0660',6,'P9'),
('1-400400-0660',7,'P9'),
('1-400400-0680',1,'P5'),
('1-400400-0680',2,'P6'),
('1-400400-0680',3,'P6'),
('1-400400-0680',4,'P8'),
('1-400400-0680',5,'P9'),
('1-400400-0680',6,'P9'),
('1-400400-0680',7,'P9'),
('1-400400-0680',8,'P9'),
('1-400400-0680',9,'P9'),
('1-400400-0680',10,'P9'),
('1-400400-0680',11,'P9'),
('1-400400-0680',12,'P9'),
('1-400400-0680',13,'P9'),
('1-400400-0680',14,'P9'),
('1-400400-0680',15,'P9'),
('1-400400-0680',16,'P9'),
('1-400400-0680',17,'P9'),
('1-400400-0680',18,'P9'),
('1-400400-0680',19,'P9'),
('1-400400-0680',20,'P9'),
('1-400400-0680',21,'P9'),
('1-400400-0680',22,'P9'),
('1-400400-0680',23,'PD'),
('1-400400-0680',24,'PD'),
('1-400400-0680',25,'PD'),
('1-400400-0680',26,'PD'),
('1-400400-0680',27,'PD'),
('1-400400-0680',28,'PD'),
('1-400400-0680',29,'PD')
使用临时数字 table 和 cross apply(values ...)
来逆透视您的数据:
;with numbers as (
select top (32) --<-- 32 works for the example, increase for larger sets
i=row_number() over(order by (select 1))
from master..spt_values
order by i
)
select
t.Position
, PositionSlot=row_number() over (
partition by t.Position
order by v.PositionSlotDescription, n.i
)
, v.PositionSlotDescription
from @table t
cross apply (values
('PD',PD),('P5',P5) ,('P6',P6) ,('P7',P7) ,('P8',P8) ,('P9',P9) ,('FT',FT)
) v (PositionSlotDescription, Amount)
inner join numbers n
on n.i <= v.amount
where v.Amount is not null
order by t.Position, v.PositionSlotDescription
rextester 演示:http://rextester.com/AMKCR70455
returns:
+---------------+--------------+-------------------------+
| Position | PositionSlot | PositionSlotDescription |
+---------------+--------------+-------------------------+
| 1-400400-0041 | 1 | FT |
| 1-400400-0660 | 1 | P8 |
| 1-400400-0660 | 2 | P9 |
| 1-400400-0660 | 3 | P9 |
| 1-400400-0660 | 4 | P9 |
| 1-400400-0660 | 5 | P9 |
| 1-400400-0660 | 6 | P9 |
| 1-400400-0660 | 7 | P9 |
| 1-400400-0680 | 1 | P5 |
| 1-400400-0680 | 2 | P6 |
| 1-400400-0680 | 3 | P6 |
| 1-400400-0680 | 4 | P8 |
| 1-400400-0680 | 5 | P9 |
| 1-400400-0680 | 6 | P9 |
| 1-400400-0680 | 7 | P9 |
| 1-400400-0680 | 8 | P9 |
| 1-400400-0680 | 9 | P9 |
| 1-400400-0680 | 10 | P9 |
| 1-400400-0680 | 11 | P9 |
| 1-400400-0680 | 12 | P9 |
| 1-400400-0680 | 13 | P9 |
| 1-400400-0680 | 14 | P9 |
| 1-400400-0680 | 15 | P9 |
| 1-400400-0680 | 16 | P9 |
| 1-400400-0680 | 17 | P9 |
| 1-400400-0680 | 18 | P9 |
| 1-400400-0680 | 19 | P9 |
| 1-400400-0680 | 20 | P9 |
| 1-400400-0680 | 21 | P9 |
| 1-400400-0680 | 22 | P9 |
| 1-400400-0680 | 23 | PD |
| 1-400400-0680 | 24 | PD |
| 1-400400-0680 | 25 | PD |
| 1-400400-0680 | 26 | PD |
| 1-400400-0680 | 27 | PD |
| 1-400400-0680 | 28 | PD |
| 1-400400-0680 | 29 | PD |
+---------------+--------------+-------------------------+
参考:
- Generate a set or sequence without loops - 1 - Aaron Bertrand
- Generate a set or sequence without loops - 2 - Aaron Bertrand
- Generate a set or sequence without loops - 3 - Aaron Bertrand
- The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
对于更大的集合,您可以将
cross join from master..spt_values
添加到上面的 number
cte,或者将 numbers
cte 替换为这个备用堆叠 cte:
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, numbers as (
select top(50000)
i=row_number() over (order by (select 1))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
)
rextester 演示:http://rextester.com/OZZHR43374
具有 UNPIVOT
和递归 CTE
--Unpivot results
if object_id('tempdb..#unpivot') is not null drop table #unpivot
select
u.Position
,u.Code
,u.Descr
into #unpivot
from
@table t
unpivot
(
code
for Descr in (PD, P5, P6, P7, P8, P9, FT)
) u
--Recursive CTE
;with cte as(
select
t.Position
,Code = 1
,t.Descr
from #unpivot t
union all
select
t2.Position
,cte.Code + 1
,t2.Descr
from #unpivot t2
inner join cte
on cte.Position = t2.Position
and cte.Descr = t2.Descr
where t2.Code > cte.Code)
--Results
select
Position
--,Code
,PositionSlotCode = row_number() over (partition by Position order by Descr)
,Descr
from
cte
order by
Position
,Descr
,Code