SQL Server - 复杂动态透视多列

时间:2014-07-23 18:39:03

标签: sql sql-server-2008 pivot

仅供参考,这个问题已经得到解答,但我有一些新要求,实施起来非常复杂,我将其作为一个新问题发布而不是编辑旧问题:(Previous Question

我有两个表“Controls”和“ControlChilds”(在ControlChilds表中我们添加了一个名为ControlChildComments的新列,我们需要在PIVOT输出中显示)

父表结构:

Create table Controls(
    ProjectID Varchar(20) NOT NULL,
    ControlID INT NOT NULL,
    ControlCode Varchar(2) NOT NULL,
    ControlPoint Decimal NULL,
    ControlScore Decimal NULL,
    ControlValue Varchar(50)
)

样本数据

ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001        1           A            30.44            65            Invalid
P001        2           C            45.30            85            Valid

子表结构:

Create table ControlChilds(
    ControlID INT NOT NULL,
    ControlChildID INT NOT NULL,
    ControlChildValue Varchar(200) NULL,
    ControlChildComments Varchar(200) NULL
)

样本数据

ControlID | ControlChildID | ControlChildValue | ControlChildComments 
1           100              Yes                 Something
1           101              No                  NULL 
1           102              NA                  Others  
1           103              Others              NULL  
2           104              Yes                 New one
2           105              SomeValue           NULL 

基于我之前的问题(Previous Question),我得到了这个输出(你可以参考在@bluefeet给出的答案中产生这个输出的PIVOT查询。再次感谢@bluefeet。)

Report format

但现在我的要求已经改变,我需要在每个Child值之后使用ControlChildComments。例如,A_Child1,A_Child1Comments,A_Child2,A_Child2Comments等...

另一个棘手的问题是我只需要在它们不为空时显示注释,否则我不应该显示该列。例如,在这种情况下,它应该是这样的:

A_Child1,A_Child1Comments,A_Child2,A_Child3,A_Child3Comments,A_Child4,C_Child1,C_Child1Comments,C_Child2

这可能吗?我尝试了很多东西,但结果并不准确。

2 个答案:

答案 0 :(得分:1)

由于您的ControlChilds表中现在有多个列需要PIVOT,因此您需要使用类似的方法来首先取消它们,并应用Controls表。

您需要使用类似于以下代码的ChildControlValueChildControlComments取消隐藏:

select 
  projectId,
  col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
  value
from
(
  select c.ProjectId,
    c.ControlCode,
    cc.ControlChildValue,
    cc.ControlChildComments,
    row_number() over(partition by c.ProjectId, c.ControlCode
                      order by cc.ControlChildId) seq
  from controls c
  inner join controlchilds cc
    on c.controlid = cc.controlid
) d
cross apply
(
  select 'ChildValue', ControlChildValue union all
  select 'ChildComments', ControlChildComments
) c (subCol, value);

SQL Fiddle with Demo。这将以以下格式获取您的数据:

| PROJECTID |              COL |     VALUE |
|-----------|------------------|-----------|
|      P001 |    A_ChildValue1 |       Yes |
|      P001 | A_ChildComments1 | Something |
|      P001 |    A_ChildValue2 |        No |
|      P001 | A_ChildComments2 |    (null) |
|      P001 |    A_ChildValue3 |        NA |

然后在现有查询中使用此代码:

select ProjectId,
  A_ControlPoint, A_ControlScore, A_ControlValue,
  A_ChildValue1, A_ChildComments1, A_ChildValue2, 
  A_ChildComments2, A_ChildValue3, A_ChildComments3,
  A_ChildValue4, A_ChildComments4,
  C_ControlPoint, C_ControlScore, C_ControlValue,
  C_Child1, C_Child2
from
(
  select 
    ProjectId,
    col = ControlCode +'_'+col,
    val
  from
  (
    select 
      c.ProjectId,
      c.ControlCode,
      c.ControlPoint,
      c.ControlScore,
      c.ControlValue
    from controls c
  ) d
  cross apply
  (
    select 'ControlPoint', cast(controlpoint as varchar(10)) union all
    select 'ControlScore', cast(ControlScore as varchar(10)) union all
    select 'ControlValue', ControlValue
  ) c (col, val)
  union all
  select 
    projectId,
    col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
    value
  from
  (
    select c.ProjectId,
      c.ControlCode,
      cc.ControlChildValue,
      cc.ControlChildComments,
      row_number() over(partition by c.ProjectId, c.ControlCode
                        order by cc.ControlChildId) seq
    from controls c
    inner join controlchilds cc
      on c.controlid = cc.controlid
  ) d
  cross apply
  (
    select 'ChildValue', ControlChildValue union all
    select 'ChildComments', ControlChildComments
  ) c (subCol, value)
) src
pivot
(
  max(val)
  for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
              A_ChildValue1, A_ChildComments1, A_ChildValue2, 
              A_ChildComments2, A_ChildValue3, A_ChildComments3,
              A_ChildValue4, A_ChildComments4,
              C_ControlPoint, C_ControlScore, C_ControlValue,
              C_Child1, C_Child2)
) piv;

SQL Fiddle with Demo。最后,您将在动态SQL脚本中实现它:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col) 
                    from 
                    (
                      select ControlCode,
                        col = ControlCode +'_'+col,
                        seq, 
                        so
                      from controls
                      cross apply
                      (
                        select 'ControlPoint', 0, 0 union all
                        select 'ControlScore', 0, 1 union all
                        select 'ControlValue', 0, 2 
                      ) c (col, seq, so)
                      union all
                      select  ControlCode,
                        col = ControlCode+'_'+subcol+cast(rn as varchar(10)),
                        rn, 
                        so
                      from
                      (
                        select ControlCode, 
                          row_number() over(partition by c.ProjectId, c.ControlCode
                                                  order by cc.ControlChildId) seq
                        from controls c
                        inner join controlchilds cc
                          on c.controlid = cc.controlid
                      ) d
                      cross apply
                      (
                        select 'ChildValue', seq, 3 union all
                        select 'ChildComments', seq, 4
                      ) c (subcol, rn, so)                      
                    ) src
                    group by ControlCode, seq, col, so
                    order by ControlCode, seq, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT ProjectId, ' + @cols + ' 
            from 
            (
              select ProjectId,
                col = ControlCode +''_''+col,
                val
              from
              (
                select 
                  c.ProjectId,
                  c.ControlCode,
                  c.ControlPoint,
                  c.ControlScore,
                  c.ControlValue
                from controls c
              ) d
              cross apply
              (
                select ''ControlPoint'', cast(controlpoint as varchar(10)) union all
                select ''ControlScore'', cast(ControlScore as varchar(10)) union all
                select ''ControlValue'', ControlValue
              ) c (col, val)
              union all
              select 
                projectId,
                col = ControlCode+''_''+subCol+cast(seq as varchar(10)),
                value
              from
              (
                select c.ProjectId,
                  c.ControlCode,
                  cc.ControlChildValue,
                  cc.ControlChildComments,
                  row_number() over(partition by c.ProjectId, c.ControlCode
                                    order by cc.ControlChildId) seq
                from controls c
                inner join controlchilds cc
                  on c.controlid = cc.controlid
              ) d
              cross apply
              (
                select ''ChildValue'', ControlChildValue union all
                select ''ChildComments'', ControlChildComments
              ) c (subCol, value)
            ) x
            pivot 
            (
                max(val)
                for col in (' + @cols + ')
            ) p '

exec sp_executesql @query;

SQL Fiddle with Demo。这两个都给出了结果:

| PROJECTID | A_CONTROLPOINT | A_CONTROLSCORE | A_CONTROLVALUE | A_CHILDVALUE1 | A_CHILDCOMMENTS1 | A_CHILDVALUE2 | A_CHILDCOMMENTS2 | A_CHILDVALUE3 | A_CHILDCOMMENTS3 | A_CHILDVALUE4 | A_CHILDCOMMENTS4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILDVALUE1 | C_CHILDCOMMENTS1 | C_CHILDVALUE2 | C_CHILDCOMMENTS2 |
|-----------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|---------------|------------------|---------------|------------------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|
|      P001 |          30.44 |          65.00 |        Invalid |           Yes |        Something |            No |           (null) |            NA |           Others |        Others |           (null) |          45.30 |          85.00 |          Valid |           Yes |          New one |     SomeValue |           (null) |

答案 1 :(得分:-2)

以下是动态交叉表的示例。由于您有多个列,因此需要调整此动态部分以适应。

if OBJECT_ID('Something') is not null
    drop table Something

create table Something
(
    ID int,
    Subject1 varchar(50)
)

insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare @MaxCols int

declare @StaticPortion nvarchar(2000) = 
    'with OrderedResults as
    (
        select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
        from Something
    )
    select ID';

declare @DynamicPortion nvarchar(max) = '';
declare @FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';

with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS 
(
    SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)

select @DynamicPortion = @DynamicPortion + 
    ', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <= 
(
    select top 1 Count(*)
    from Something
    group by ID
    order by COUNT(*) desc
)

select @StaticPortion + @DynamicPortion + @FinalStaticPortion

--declare @SqlToExecute nvarchar(max) = @StaticPortion + @DynamicPortion + @FinalStaticPortion;
--exec sp_executesql @SqlToExecute