SQL Server - 复杂动态数据透视表列

时间:2014-06-12 14:08:37

标签: sql sql-server-2008 pivot

我有两张桌子" Controls"和" ControlChilds"

父表结构:

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 
)

样本数据

ControlID | ControlChildID | ControlChildValue
1           100              Yes
1           101              No
1           102              NA  
1           103              Others 
2           104              Yes
2           105              SomeValue

对于给定的ProjectID,输出应该在一行中,其所有的Control值都是&其次是子控件值(基于ControlCode(即)ControlCode_Child(1,2,3 ......),它应该看起来像这样

Report format

此外,我尝试了这个PIVOT查询,我能够获取ChildControls表值,但我不知道如何获取Controls表值。

DECLARE @cols AS NVARCHAR(MAX);

DECLARE @query AS NVARCHAR(MAX);
select @cols = STUFF((SELECT 
                        distinct ',' + 
                        QUOTENAME(ControlCode + '_Child' + CAST(ROW_NUMBER() over(PARTITION BY ControlCode ORDER BY ControlChildID) AS Varchar(25)))
                      FROM Controls C
                      INNER JOIN ControlChilds CC 
                      ON C.ControlID = CC.ControlID 
                      FOR XML PATH(''), TYPE
                     ).value('.', 'NVARCHAR(MAX)') 
                        , 1, 1, '');

SELECT @query ='SELECT *
FROM
(
  SELECT   
    (ControlCode + ''_Child'' + CAST(ROW_NUMBER() over(PARTITION BY ControlCode ORDER BY ControlChildID) AS Varchar(25))) As Code,
        ControlChildValue
  FROM Controls AS C
  INNER JOIN ControlChilds AS CC ON C.ControlID = CC.ControlID
) AS t
PIVOT 
(
  MAX(ControlChildValue) 
  FOR Code IN( ' + @cols + ' )' +
' ) AS p ; ';

 execute(@query);

我得到的输出: enter image description here

有谁可以帮我解决如何在每个ControlChilds表值前面获取Controls表值?

2 个答案:

答案 0 :(得分:6)

你在这里有点乱,因为你有两个不同结构的表,你想要转动多个列。我首先开始编写查询的静态版本以使逻辑正确,然后完成编写动态版本的过程。

由于您想要转动多个列,因此您需要首先取消Controls表中的多个列,然后转动。您已将其标记为SQL Server 2008,因此您可以使用CROSS APPLY取消对列的拆分。

我建议采取以下步骤。首先,取消忽略controls表:

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)

SQL Fiddle with Demo。这会将您的多行转换为多个列,类似于:

| PROJECTID |            COL |     VAL |
|-----------|----------------|---------|
|      P001 | A_ControlPoint |   30.44 |
|      P001 | A_ControlScore |   65.00 |
|      P001 | A_ControlValue | Invalid |
|      P001 | C_ControlPoint |   45.30 |
|      P001 | C_ControlScore |   85.00 |
|      P001 | C_ControlValue |   Valid |

其次,将ControlChilds表中的数据转换为类似的格式,但使用row_number()为每个子项分配序列:

select 
  projectId,
  col = ControlCode+'_'+'Child'+cast(seq as varchar(10)),
  ControlChildValue
from
(
  select c.ProjectId,
    c.ControlCode,
    cc.ControlChildValue,
    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

SQL Fiddle with Demo。这将从以下格式获取此表中的数据:

| PROJECTID |      COL | CONTROLCHILDVALUE |
|-----------|----------|-------------------|
|      P001 | A_Child1 |               Yes |
|      P001 | A_Child2 |                No |
|      P001 | A_Child3 |                NA |
|      P001 | A_Child4 |            Others |
|      P001 | C_Child1 |               Yes |
|      P001 | C_Child2 |         SomeValue |

现在,您可以在两个查询之间轻松使用UNION ALL并应用PIVOT函数:

select ProjectId,
  A_ControlPoint, A_ControlScore, A_ControlValue,
  A_Child1, A_Child2, A_Child3, A_Child4,
  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+'_'+'Child'+cast(seq as varchar(10)),
    ControlChildValue
  from
  (
    select c.ProjectId,
      c.ControlCode,
      cc.ControlChildValue,
      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
) src
pivot
(
  max(val)
  for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
              A_Child1, A_Child2, A_Child3, A_Child4,
              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+'_'+'Child'+cast(seq as varchar(10)),
                        seq, 
                        3
                      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
                    ) src
                    group by ControlCode, seq, col, so
                    order by ControlCode, so, seq
            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+''_Child''+cast(seq as varchar(10)),
                ControlChildValue
              from
              (
                select c.ProjectId,
                  c.ControlCode,
                  cc.ControlChildValue,
                  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
            ) 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_CHILD1 | A_CHILD2 | A_CHILD3 | A_CHILD4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILD1 |  C_CHILD2 |
|-----------|----------------|----------------|----------------|----------|----------|----------|----------|----------------|----------------|----------------|----------|-----------|
|      P001 |          30.44 |          65.00 |        Invalid |      Yes |       No |       NA |   Others |          45.30 |          85.00 |          Valid |      Yes | SomeValue |

答案 1 :(得分:1)

PIVOT您的数据首先需要UNPIVOT部分数据,Controls表中的部分。

准备查询成为

SELECT ProjectID, ControlCode + '_' + [Field] [Field], Value
FROM   (SELECT ProjectID
             , ControlCode
             , CAST(ControlPoint AS SQL_Variant) ControlPoint
             , CAST(ControlScore AS SQL_Variant) ControlScore
             , CAST(ControlValue AS SQL_Variant) ControlValue
        FROM Controls C) D
       UNPIVOT
       (Value FOR [Field] IN (ControlPoint, ControlScore, ControlValue)) p
UNION ALL
SELECT ProjectID, ControlCode + '_Child' + RowID [Field], Value
FROM   (SELECT C.ProjectID
             , C.ControlCode
             , RowID = CAST(ROW_NUMBER() 
                 OVER (PARTITION BY CC.ControlID 
                       ORDER BY CC.ControlChildID) AS VARCHAR)
             , CAST(CC.ControlChildValue AS SQL_Variant) ControlChildValue
        FROM Controls C
             INNER JOIN ControlChilds CC ON C.ControlID = CC.ControlID) D
       UNPIVOT
       (Value FOR [Field] IN (ControlChildValue)) p

SQLFiddle demo

以格式

获取数据
PROJECTID | FIELD          | VALUE
----------+----------------+----------
P001      | A_ControlPoint | 30
P001      | A_ControlScore | 65
P001      | A_ControlValue | Invalid
P001      | C_ControlPoint | 45
P001      | C_ControlScore | 85
P001      | C_ControlValue | Valid
P001      | A_Child1       | Yes
P001      | A_Child2       | No
P001      | A_Child3       | NA
P001      | A_Child4       | Others
P001      | C_Child1       | Yes
P001      | C_Child2       | SomeValue

现在可以进入PIVOT

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

WITH Q AS (
  SELECT ProjectID, ControlCode + '_' + [Field] [Field], Value
       , ControlCode
       , ID = CASE WHEN [Field] = 'ControlPoint' THEN 1
                   WHEN [Field] = 'ControlScore' THEN 2
                   WHEN [Field] = 'ControlValue' THEN 3
              END
  FROM   (SELECT ProjectID
               , ControlCode
               , CAST(ControlPoint AS SQL_Variant) ControlPoint
               , CAST(ControlScore AS SQL_Variant) ControlScore
               , CAST(ControlValue AS SQL_Variant) ControlValue
          FROM Controls C) D
         UNPIVOT
         (Value FOR [Field] IN (ControlPoint, ControlScore, ControlValue)) p
  UNION ALL
  SELECT ProjectID, ControlCode + '_Child' + RowID [Field], Value
       , ControlCode
       , ID = RowID + 3
  FROM   (SELECT C.ProjectID
               , C.ControlCode
               , RowID = CAST(ROW_NUMBER() OVER (PARTITION BY CC.ControlID ORDER BY CC.ControlChildID) AS VARCHAR)
               , CAST(CC.ControlChildValue AS SQL_Variant) ControlChildValue
          FROM Controls C
               INNER JOIN ControlChilds CC ON C.ControlID = CC.ControlID) D
         UNPIVOT
         (Value FOR [Field] IN (ControlChildValue)) p
)
SELECT @cols = STUFF((SELECT ',' + QUOTENAME([Field])
                      FROM Q
                      ORDER BY ControlCode, ID
                      FOR XML PATH(''), TYPE
                     ).value('.', 'NVARCHAR(MAX)') 
                        , 1, 1, '')

SELECT @query ='
WITH Q AS (
  SELECT ProjectID, ControlCode + ''_'' + [Field] [Field], Value
  FROM   (SELECT ProjectID
               , ControlCode
               , CAST(ControlPoint AS SQL_Variant) ControlPoint
               , CAST(ControlScore AS SQL_Variant) ControlScore
               , CAST(ControlValue AS SQL_Variant) ControlValue
          FROM Controls C) D
         UNPIVOT
         (Value FOR [Field] IN (ControlPoint, ControlScore, ControlValue)) p
  UNION ALL
  SELECT ProjectID, ControlCode + ''_Child'' + RowID [Field], Value
  FROM   (SELECT C.ProjectID
               , C.ControlCode
               , RowID = CAST(ROW_NUMBER() OVER (PARTITION BY CC.ControlID ORDER BY CC.ControlChildID) AS VARCHAR)
               , CAST(CC.ControlChildValue AS SQL_Variant) ControlChildValue
          FROM Controls C
               INNER JOIN ControlChilds CC ON C.ControlID = CC.ControlID) D
         UNPIVOT
         (Value FOR [Field] IN (ControlChildValue)) p
)
SELECT *
FROM   (SELECT ProjectID, [Field], Value FROM Q) AS t
       PIVOT 
       (MAX(Value) FOR [Field] IN( ' + @cols + ' )) AS p ;'

execute(@query);

SQLFiddle demo

要按所需顺序获取字段,必须在列字符串中强制执行特定顺序,因此在SELECT ... FOR XML中,由于没有可用的订单列,因此需要创建新订单,即ID

中的CTE
相关问题