/ / PIVOT con múltiples columnas - sql-server, sql-server-2008, pivot, unpivot

PIVOT con múltiples columnas - sql-server, sql-server-2008, pivot, unpivot

Estoy tratando de pivotar en dos columnas en SQL Server 2008 en una tabla de facturas. Así que tengo datos como los siguientes:

+--------------+--------+---------+------+
| Invoice Date | Item # | Dollars | Lbs. |
+--------------+--------+---------+------+
| 1/1/14       | A      |       1 |    1 |
| 1/2/14       | B      |       2 |    2 |
| 1/3/14       | A      |       3 |    3 |
| 1/4/14       | B      |       4 |    4 |
| 2/1/14       | A      |       5 |    5 |
| 2/1/14       | B      |       6 |    6 |
+--------------+--------+---------+------+

Me gustaría mostrarlo como

+--------+--------------+-----------------+--------------+-----------------+
| Item # | 1/31/14 Lbs. | 1/31/14 Dollars | 2/28/14 Lbs. | 2/28/14 Dollars |
+--------+--------------+-----------------+--------------+-----------------+
| A      |            4 |               4 |            5 |               5 |
| B      |            6 |               6 |            6 |               6 |
+--------+--------------+-----------------+--------------+-----------------+

Tenga en cuenta que el nombre de la columna es el último día de ese mes y en dólares o libras. Puedo hacerlo solo una columna (ya sea en libras o en dólares) sin embargo no puedo hacerlo en ambas.

Aquí está mi código de ejemplo para sólo libras:

DECLARE
@v_Columns VARCHAR(MAX),
@v_Query VARCHAR(MAX)

--pivot and delimit values

SELECT @v_Columns = COALESCE(@v_Columns,"[") + convert(varchar(8), InvoiceDate, 1) + " Lbs." + "],["
FROM
( SELECT DISTINCT dbo.ufn_GetLastDayOfMonth(InvoiceDate) As InvoiceDate
FROM Invoice
WHERE InvoiceDate BETWEEN @BEGIN_DATE AND @END_DATE
ORDER BY InvoiceDate

--delete last two chars of string (the ending ",[")

SET @v_Columns = SUBSTRING(@v_Columns, 1, LEN(@v_Columns)-2)
PRINT @v_Columns

--construct sql statement

SET @v_Query =

"WITH AllOrders (LastInvoiceDate, Item,  Pounds) AS
(
SELECT
CONVERT(varchar(8), dbo.ufn_GetLastDayOfMonth(Invoice.InvoiceDate), 1) + """ + " Lbs." + """ As LastInvoiceDate,
Item,
Pounds
FROM INVOICE
WHERE InvoiceDate BETWEEN @BEGIN_DATE AND  @END_DATE
)
SELECT *
FROM AllOrders
PIVOT
(
SUM(QuantityShipped)
FOR LastInvoiceDate  IN (" + @v_Columns + ")
) AS pivotview"

¡Gracias a todos de antemano!

Respuestas

2 para la respuesta № 1

Para obtener el resultado, deberá PIVOTAR dos veces o UNPIVOT el Dollars y Lbs columnas en una sola columna y luego aplicar el PIVOT una vez. Mi preferencia sería quitar el pivote y luego girar porque me resulta mucho más fácil.

En lugar de trabajar dinámicamente primero, deberíasescriba la consulta como una versión estática o codificada para obtener la lógica correcta, luego conviértala a SQL dinámico. El ejemplo que tengo usa tus fechas finales. 201-01-31, etc. porque está utilizando una función para crear esas fechas y debe poder aplicarla según sea necesario.

Ya que está utilizando SQL Server 2005+, puede usar CROSS APPLY para quitar la imagen Dollars y Lbs. El código será similar al siguiente:

select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + "_"+ c.col,
c.value
from yourtable t
cross apply
(
select "Dollars", Dollars union all
select "Lbs", Lbs
) c (col, value);

Ver SQL Fiddle con demostración. Esto convierte sus datos al siguiente formato:

| ITEMNO |            NEW_COL | VALUE |
|--------|--------------------|-------|
|      A | 2014-01-31_Dollars |     1 |
|      A |     2014-01-31_Lbs |     1 |
|      B | 2014-01-31_Dollars |     2 |
|      B |     2014-01-31_Lbs |     2 |
|      A | 2014-01-31_Dollars |     3 |

He concatenado en new_col los últimos nombres de columna que necesitará. Una vez más, puede formatear la fecha en el formato que necesite, que acabo de usar 2014-01-31 y agregó el Dollars o Lbs hasta el final de ello. Una vez que haya obtenido los datos, PIVOTARÁ los valores en su resultado final deseado:

select ItemNo,
[2014-01-31_Lbs], [2014-01-31_Dollars],
[2014-02-28_Lbs], [2014-02-28_Dollars]
from
(
select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + "_"+ c.col,
c.value
from yourtable t
cross apply
(
select "Dollars", Dollars union all
select "Lbs", Lbs
) c (col, value)
) d
pivot
(
sum(value)
for new_col in ([2014-01-31_Lbs], [2014-01-31_Dollars],
[2014-02-28_Lbs], [2014-02-28_Dollars])
) p;

Ver SQL Fiddle con demostración. Ahora tiene el resultado que desea, así que simplemente conviértalo a SQL dinámico:

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

select @cols = STUFF((SELECT "," + QUOTENAME(convert(varchar(10), t.[invoice date], 120) + "_"+ c.col)
from yourtable t
cross apply
(
select "Lbs", 0 union all
select "Dollars", 1
) c (col, so)
group by [invoice date], col, so
order by [invoice date], so
FOR XML PATH(""), TYPE
).value(".", "NVARCHAR(MAX)")
,1,1,"")


set @query = "SELECT ItemNo," + @cols + "
from
(
select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + ""_""+ c.col,
c.value
from yourtable t
cross apply
(
select ""Dollars"", Dollars union all
select ""Lbs"", Lbs
) c (col, value)
) d
pivot
(
sum(value)
for new_col in (" + @cols + ")
) p "

exec sp_executesql @query;

Ver SQL Fiddle con demostración. Esto da un resultado final de:

| ITEMNO | 2014-01-31_LBS | 2014-01-31_DOLLARS | 2014-02-28_LBS | 2014-02-28_DOLLARS |
|--------|----------------|--------------------|----------------|--------------------|
|      A |              4 |                  4 |              5 |                  5 |
|      B |              6 |                  6 |              6 |                  6 |

0 para la respuesta № 2

Aquí está su tabla de muestra

  CREATE TABLE #TEMP([Invoice Date] DATE,[Item #] VARCHAR(10),[DollarS] NUMERIC(10,0),[Lbs.] NUMERIC(10,0))
INSERT INTO #TEMP VALUES ("1/1/14", "A",1,1)
INSERT INTO #TEMP VALUES ("1/2/14", "B",2,2)
INSERT INTO #TEMP VALUES ("1/3/14", "A",3,3)
INSERT INTO #TEMP VALUES ("1/4/14", "B",4,4)
INSERT INTO #TEMP VALUES ("2/1/14", "A",5,5)
INSERT INTO #TEMP VALUES ("2/1/14", "B",6,6)

Ahora necesitas aplicar UNION ALL(en lugar de UNPIVOT) y coloque las columnas en fila y combine las columnas, obtenga el orden de las columnas como Date+LBS/DOLLARS.

SELECT DISTINCT DENSE_RANK() OVER(ORDER BY  CAST(LASTDAY AS DATE),UNIT DESC)RNO,*,
CAST(DATEPART(MONTH,LASTDAY)AS VARCHAR) +"/"+ CAST(DATEPART(DAY,LASTDAY)AS VARCHAR) +"/" +RIGHT(CAST(YEAR(LASTDAY)AS VARCHAR),2)+" " +UNIT  PIVOTCOL
INTO #NEWTABLE
FROM
(
SELECT [Item #],"DOLLARS" UNIT,
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
SUM([Dollars]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) VALUE
FROM #TEMP

UNION ALL

SELECT [Item #], "LBS.",
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
SUM([Lbs.]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) DOLLARSUM
FROM #TEMP
)TAB

Ahora declare la consulta para obtener las columnas dinámicamente y para establecer NULL to Zero

DECLARE @cols NVARCHAR (MAX)
DECLARE @NullToZeroCols NVARCHAR (MAX)

SELECT @cols = COALESCE (@cols + ",[" + PIVOTCOL + "]",
"[" + PIVOTCOL + "]")
FROM    (SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE) PV
ORDER BY RNO
PRINT @COLS

SET @NullToZeroCols = SUBSTRING((SELECT ",ISNULL(["+PIVOTCOL+"],0) AS ["+PIVOTCOL+"]"
FROM(SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE GROUP BY RNO,PIVOTCOL)TAB
ORDER BY RNO  FOR XML PATH("")),2,8000)

Ahora pivota la consulta

DECLARE @query NVARCHAR(MAX)
SET @query = "SELECT [Item #]," + @NullToZeroCols + " FROM
(
SELECT [Item #],VALUE,PIVOTCOL FROM #NEWTABLE
) x
PIVOT
(
SUM(VALUE)
FOR PIVOTCOL IN (" + @cols + ")
) p
ORDER BY [Item #];"

EXEC SP_EXECUTESQL @query

RESULTADO

enter image description here