Sql 来自多个列的动态/条件枚举

Sql 来自多个列的动态/条件枚举,sql,sql-server,sql-server-2016,Sql,Sql Server,Sql Server 2016,我试图找出将多列枚举为两行的最佳方法。例如,以下数据包含公司每个职位的员工总数,按全职同等(FTE)状态细分 因此,对于职位1-400400-0680,有29名员工,如上所述 我需要列举这些集合,为每个职位创建一个与FTE分组相关的连续职位槽 每个位置的总行数应等于列PD、P5、P6、P7、P8、P9和FT的总和 PositionsSlotDescription应该是原始列名PD、P5、P6、P7、P8、P9和FT PositionSlot应该是这些行的顺序编号插槽的顺序无关紧要。因此Posi

我试图找出将多列枚举为两行的最佳方法。例如,以下数据包含公司每个职位的员工总数,按全职同等(FTE)状态细分

因此,对于
职位1-400400-0680
,有29名员工,如上所述

我需要列举这些集合,为每个职位创建一个与FTE分组相关的连续职位槽

  • 每个
    位置的总行数应等于列
    PD、P5、P6、P7、P8、P9和FT的总和
    
  • PositionsSlotDescription
    应该是原始列名
    PD、P5、P6、P7、P8、P9和FT
  • PositionSlot
    应该是这些行的顺序编号插槽的顺序无关紧要。因此
    PositionSlot=1
    可以属于任何
    positionslotdescription
  • 应重复
    PositionsSlotDescription
    / 根据原始聚合中的数字复制
例如,
位置1-400400-0660
有1个用于
P8
,6个用于
P9
。因此,应该有1行带有
P8
PositionSlotDescription
,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')

使用临时数字表和交叉应用(值…)
取消对数据的预存:

;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

参考:


对于较大的集合,您可以将主..spt_值的
交叉连接添加到上面的
编号
cte中,或者用此备用堆叠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演示:

一个带有
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

Zim给人留下深刻印象的是,您花了很短的时间才弄明白这一点。@scsimon您包括了创建ddl和预期结果,这使它非常容易开始。这是一个有趣的问题,也很有帮助!哈,我同意!当我在这方面找不到任何东西时,我有点震惊,但也许我的问题/想法太复杂了。我有一个有效的解决方案,但它涉及到一个非PIVOT和递归CTE。我想我更喜欢你的。我要看看它是否能伸缩。我想会的。我稍后会发布我的,以供参考。@scsimon如果迭代次数很小,递归cte不应该是一个障碍;否则,我会猜测递归在比较中会表现不佳(类似于我的答案中引用的Aaron Bertrand文章中的基准测试结果)。当我根据生产数据测试时,我肯定会让您知道(我还没有回来)。我读过亚伦的那篇文章,还有他的大部分其他文章。你现在更快了,因为我必须取消Pivot(发布在下面)
;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
)
--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