sql >> Base de Datos >  >> RDS >> Sqlserver

SQL Server:columnas dinámicas dinámicas complejas

Tiene un poco de lío aquí porque tiene dos tablas en diferentes estructuras y desea pivotar varias columnas. Primero comenzaría escribiendo una versión estática de su consulta para obtener la lógica correcta, luego pasaría por el proceso de escribir una versión dinámica.

Dado que desea girar varias columnas, deberá deshacer la rotación de varias columnas en los Controls primero la mesa, luego pivotar. Ha etiquetado esto como SQL Server 2008, por lo que puede usar CROSS APPLY para des-pivotar las columnas.

Sugiero tomar los siguientes pasos. Primero, quita el pivote de los controls tabla:

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)

Consulte SQL Fiddle con demostración . Esto va a convertir sus múltiples filas en múltiples columnas similares a:

| 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 |

En segundo lugar, obtenga los datos de ControlChilds tabla en un formato similar pero use su row_number() para asignar una secuencia para cada niño:

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

Consulte SQL Fiddle con demostración . Esto obtiene los datos de esta tabla en el formato:

| 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 |

Ahora, puede usar fácilmente UNION ALL entre las dos consultas y aplique la función 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;

Consulte SQL Fiddle con demostración .

Ahora que tiene la lógica correcta, puede convertir esto a una versión SQL dinámica:

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;

Consulte SQL Fiddle con demostración . Escribí la versión dinámica para mantener las columnas en el orden que usó en su muestra. Esto se puede hacer usando un tipo de valor de orden de clasificación.

Esto da un resultado final de:

| 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 |