SQL Pivot con un número variable de filas y clave compuesta para formar columnas

Pregunta dinámica de SQL con un giro, tome la siguiente tabla (tenga en cuenta que el año y el mes están separados):

CREATE TABLE [dbo].[tbl_BranchTargets]
    (
      [BranchID] [varchar](4) NOT NULL ,
      [Year] [smallint] NOT NULL ,
      [Month] [smallint] NOT NULL ,
      [Target] [int] NULL ,
      CONSTRAINT [PK_tbl_BranchTargets] PRIMARY KEY CLUSTERED ( [BranchID] ASC, [Year] ASC, [Month] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
    )
ON  [PRIMARY]

y los siguientes datos ficticios:

INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 4, 1)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 5, 117)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 6, 233)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 7, 386)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 4, 2)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 6, 234)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 7, 387)

¿Cómo modelaría los datos ficticios dados de esta manera (tenga en cuenta que las columnas clave de año y mes se fusionan para formar AAAAMM):

Raw Data

dentro de esto:

Rendimiento esperado

Tenga en cuenta la entrada que falta para la rama 3 en mayo, esto debe manejarse como nulo. Por ejemplo, de los 12 meses, una sucursal podría tener solo un objetivo para 1 de ellos, por lo que todos los demás meses tendrían que ser nulos.

He investigado PIVOT() y las opciones de cursor torpes, pero estoy luchando por encontrar una forma rápida de mejores prácticas para hacer esto, asumo que necesito implementar algo de SQL + PIVOT() dinámico, pero no puedo conseguir mi cabeza redondearlo

Sé que para los pivotes dinámicos primero identifica los nombres de las columnas (creo), puedo hacer esto de la siguiente manera:

DECLARE @Columns AS NVARCHAR(MAX);
DECLARE @StrSQL AS NVARCHAR(MAX); 

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR, c.YEAR) + RIGHT('00' + CONVERT(VARCHAR, c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

Pero la forma en que realiza el pivote está un poco más allá de mí (ya que esencialmente estoy fusionando columnas clave para crear las columnas finales). ¿Necesitaría fusionar los datos antes de intentar el pivote donde YYYY + MM se define como un valor en 1 columna? ?

(Estoy usando SQL Server 2008 R2)

preguntado el 31 de julio de 12 a las 15:07

3 Respuestas

Estuviste muy cerca de la respuesta final. Puedes usar un PIVOT similar a la siguiente (Ver Violín SQL con demostración):

DECLARE @Columns AS NVARCHAR(MAX)
DECLARE @StrSQL AS NVARCHAR(MAX) 

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR(4), c.YEAR) + RIGHT('00' + CONVERT(VARCHAR(2), c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

set @StrSQL = 'SELECT branchid, ' + @Columns + ' from 
            (
                select branchid
                    , target
                    , CONVERT(VARCHAR(4), [YEAR]) + RIGHT(''00'' + CONVERT(VARCHAR(2), [MONTH]), 2) dt
                from tbl_BranchTargets
           ) x
            pivot 
            (
                sum(target)
                for dt in (' + @Columns + ')
            ) p '


execute(@StrSQL)

Esto creará la lista de columnas que desea en el momento de la ejecución.

Respondido 31 Jul 12, 15:07

Solo necesitaba agregar GROUP BY y ORDER BY al FOR XML, funciona perfecto, saludos - HeavenCore

Llegamos a casi exactamente la misma respuesta casi al mismo tiempo :-). Agregaría la longitud del varchar en su CASTSin embargo, es una buena práctica usar siempre VARCHAR con longitudes explícitamente - Lamak

Estabas cerca. Prueba esto:

DECLARE @Columns AS NVARCHAR(MAX)
DECLARE @StrSQL AS NVARCHAR(MAX)

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR(4), c.YEAR) + RIGHT('00' + CONVERT(VARCHAR(2), c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

SET @StrSQL = '
SELECT *
FROM (SELECT BranchId, 
             CONVERT(VARCHAR(4), [YEAR]) + RIGHT(''00'' + CONVERT(VARCHAR(2), [MONTH]), 2) YearMonth,
             Target
      FROM [dbo].[tbl_BranchTargets]) T
PIVOT(MIN(Target) FOR YearMonth IN ('+@Columns+')) AS PT'

EXEC(@StrSQL)

Aquí es un sqlfiddle para ver los resultados.

Respondido 31 Jul 12, 15:07

Tiene dos problemas: construir los períodos y pivotar

Esto construye los períodos...

declare @strPeriod nvarchar(1000)
select @strPeriod=N''
select @strPeriod = @strPeriod + ', ['+YearMonth +']'
from
(
     select distinct convert(varchar(4),tbl_BranchTargets.Year)
     + right('0'+convert(varchar(2),tbl_BranchTargets.Month),2) as YearMonth 
     from tbl_BranchTargets
) src

select @strPeriod = substring(@strPeriod, 3,LEN(@strPeriod))

Y esto hace el pivote

declare @sql nvarchar(4000)

select @sql = N'select * from 
     (select BranchID, convert(varchar(4),tbl_BranchTargets.Year)+right(''0''+
     convert(varchar(2),tbl_BranchTargets.Month),2) as YearMonth, Target from tbl_BranchTargets) 
     src 
     PIVOT 
     (sum(target) for YearMonth in ('+@strPeriod+'))p' 

exec (@sql)

Respondido 31 Jul 12, 15:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.