martes, 23 de marzo de 2010

PIVOT Dinamico SQL SERVER

Como podemos hacer un PIVOT dinamico en SQL Server??

Lo que vamos a hacer es crear un string que contenga la consulta que vamos a crear con la cantidad de valores que nosotros queramos en forma dinamica

me refiero si tenemos esto


para pivotear esto la documentacion dice que se debe hacer de la siguiente forma
link :
--------------------------------------
ejemplo link:
DaysToManufacture----AverageCost
0-----------------------------5.0885
1-----------------------------223.88
2-----------------------------359.1082
4-----------------------------949.4105

para pivotear

SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;


el resultado será

Cost_Sorted_By_Production_Days 0 1 2 3 4
AverageCost 5.0885 223.88 359.1082 NULL 949.4105

-----------------------------------------------------------

pero si se fijan ahi se debe incluir todos los valores de los dias, es decir que si existen 1000 dias se deben listar de 1 a 1000 días ([0], [1], [2], [3], [4]....[1000]), para hacer esto dinamico y si vemos que no solo pueden ser dias, sino, tipos de documentos, fechas, nombre, cuentas, etc, etc, recordar los nombre y escriibirlos uno a uno no es muy agradable entonces podemos hacer lo siguiente para crear dinamicamente el select que nos mostrará nuestro valores pivoteados tal como queremos.

continuando con mi ejemplo

el query que creará nuestro query es el siguiente:



DECLARE @Tipo VARCHAR(10) -- el largo 10 corresponde con el largo maximo del valor tipo que se pivoteara, si es mayor se debe aumentar
DECLARE @PVT VARCHAR(MAX)
DECLARE @PVT1 VARCHAR(MAX)
DECLARE @PVT2 VARCHAR(MAX)

SELECT @Tipo = MIN(Tipo) FROM TipoValor WHERE Tipo>'' -- select al menor tipo

SET @PVT1 = '' --en esta variable guardaremos los tipos entre comillas ''
SET @PVT2 = '' --en esta variable guardaremos los tipos entre corchetes []

--crearemos un ciclo para recorrer todos los tipos
WHILE NOT(@Tipo IS NULL)-- cuando se acaben los tipos de fechas
BEGIN
IF @PVT1 <> ''
SET @PVT1 = @PVT1 + ', '
SET @PVT1 = @PVT1+''''+@Tipo+'''' --tipos entre comillas

IF @PVT2 <> ''
SET @PVT2 = @PVT2 + ', '
SET @PVT2 = @PVT2+'['+@Tipo+']' --tipos entre corchetes

SELECT @Tipo = MIN(Tipo) FROM TipoValor WHERE Tipo > @Tipo --buscamos el siguente tipo de fechas mayor al anterior
END -- fin del ciclo

-- en la variable @PVT guadaremos como string el query del pivot
SELECT @PVT = 'SELECT Nombre,
'+@PVT2+ --tipos con corchetes
' FROM (SELECT
Nombre
Tipo,
Valor FROM
TipoValor WHERE
Tipo IN
( '+@PVT1+' )) as FEOC --tipos con comillas
PIVOT (MAX(Valor) -- el MAX es por si existe mas de un Tipo, se puede Sumar o agrgar cualquier funcion de agregacion
FOR Tipo IN (
'+@PVT2+' )) as P
GROUP BY
Nombre, '+@PVT2

SELECT @PVT -- Vemos el string donde esta el query del PIVOT completo

EXEC(@PVT) -- con el comando EXEC podemos ejecutar el string, que nos retornara el resultado que queremos


Todo esto nos deberia retornar los siguiente:



asi logramos agrupar por nombre, y pivoteamos el valor y los tipos, donde hay que notar el tipo T3 y que se repite para los nombre nom1 y nom3, donde podemos ver facilmente cuales típos y que valor tiene cada nombre

esta es la mejor opcion de mostrar la informacion, porque la otra opcion y que no es buena de mostrar es la siguiente

Select Nombre, Tipo, Valor from TipoValor order by Nombre

que mostrara algo asi como que no es lo optimo.

7 comentarios:

Paulonovich dijo...

El @PVT siempre estara en comillas? porque yo he intentado enviarle parametros (lo encerre en un procedure). Le envio por ejemplo @agencia='Lima'

Pero al momento de insertarlo al @PVT aparece como solo Lima, borrandose la comilla simple. Sabes de alguna forma de solucionar esto?

leochis dijo...

Hola necesito si me puedes ayudar con mi problema que no doy con la solucion, tiene algo que ver con pivot pero no se muy bien como utilizarlo.-
El tema es el siguiente:
1- Tengo que mostrar el resultado de a 31 columas, en la primer columna UNIDNEGO y en las siguientes 30 las FECHAS.-

estos son los datos:

SELECT UNIDNEGO, CLIENTE, A_FECHAESTADIA FROM PEDIDOS

UNIDNEGO CLIENTE A_FECHAESTADIA
--------- ---------- -----------------------
417 010001159 2011-12-18 00:00:00.000
417 010001159 2011-12-19 00:00:00.000
417 010001159 2011-12-20 00:00:00.000
417 010001159 2011-12-21 00:00:00.000
301 010001158 2011-12-27 00:00:00.000
301 010001158 2011-12-28 00:00:00.000
301 010001158 2011-12-29 00:00:00.000
...

tendria que quedar algo asi:

UNIDNEGO 2011-12-18 2011-12-19 2011-12-20 2011-12-21 2011-12-22 2011-12-23 2011-12-24 2011-12-25 2011-12-26 2011-12-27 2011-12-28 2011-12-29 ...
--------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ...
301 010001158 010001158 010001158 ...
417 010001159 010001159 010001159 010001159 ...

desde ya gracias

Anónimo dijo...

Hello thегe! Do you use Twittеr? ӏ'd like to follow you if that would be ok. I'm
ԁefinitely enјoуing your blog and lοοκ forward to new postѕ.



Ηave a look at my ωeb site cialis prix

Anónimo dijo...

Buen dia. Mi problema es que no logro que corra el query. Tengo 22 sitios de acopio y requiero que esos sitios de acopio sean los encabezados. Me sae el error de Oracle: ORA-01748: only simple column names allowed here
Les agradezco su ayuda.

SELECT * FROM (SELECT PSA_ANO,PSA_MES,PSA_VALOR FROM (
SELECT c.PSA_ANO, c.PSA_MES, c.PSA_VALOR
FROM DIES_SITIOS_ACOPIO b INNER JOIN DIES_PRECIOS_SITIOS_ACOPIO c ON (b.ACO_ID = c.ACO_ID)))
PIVOT (abs(PSA_VALOR) FOR b.ACO_DESCRIPCION IN (SELECT ACO_DESCRIPCION FROM DIES_SITIOS_ACOPIO))

Unknown dijo...

Agrego un detalle en el código gracias al amigo Luis Hilario que me ha ayudado a encontrar este error.

Se agrega una coma luego de los campos nombre tanto en el Select como en el Group By.

Un Saludo

luish dijo...

-- ME SALIO, GRACIAS A LA AYUDA DE SERGIO VALENZUELA

DECLARE @Tipo VARCHAR(16)
DECLARE @PVT VARCHAR(MAX)
DECLARE @PVT1 VARCHAR(MAX)
DECLARE @PVT2 VARCHAR(MAX)

SELECT @Tipo = MIN(code) FROM yconta WHERE code >'' -- select al menor tipo

SET @PVT1 = '' --en esta variable guardaremos los tipos entre comillas ''
SET @PVT2 = '' --en esta variable guardaremos los tipos entre corchetes []

--crearemos un ciclo para recorrer todos los tipos

WHILE NOT(@Tipo IS NULL)
BEGIN
IF @PVT1 <> ''
SET @PVT1 = @PVT1 + ', '
SET @PVT1 = @PVT1+''''+@Tipo+''''

IF @PVT2 <> ''
SET @PVT2 = @PVT2 + ', '
SET @PVT2 = @PVT2+'['+@Tipo+']'
SELECT @Tipo = MIN(code) FROM yconta WHERE code > @Tipo
END

-- en la variable @PVT guadaremos como string el query del pivot
SELECT @PVT = 'SELECT codEsp
,'+@PVT2+
' FROM (SELECT codEsp,code, tot FROM yconta WHERE
code IN
( '+@PVT1+' )) as FEOC --tipos con comillas
PIVOT (MAX(tot)
FOR code IN (
'+@PVT2+' )) as P
GROUP BY
codEsp, '+@PVT2

SELECT @PVT

EXEC(@PVT)

Anónimo dijo...

Saludos a todos, deseo hacer una pregunta y pretendo ser lo mas claro posible.

Tengo una tabla llamada "Asistencias" con los siguientes registros:
MATRICULA NOMBRE HORA FECHA
1 Alumno X 17:02:00 23/03/2016
1 Alumno X 17:00:00 24/03/2016
1 Alumno X 17:06:00 25/03/2016
2 Alumno Y 17:01:00 23/03/2016
2 Alumno Y 17:03:00 24/03/2016
3 Alumno Z 17:05:00 23/03/2016
3 Alumno Z 17:04:00 24/03/2016
3 Alumno Z 17:08:00 25/03/2016

En esta tabla, un Alumno X ingresa una Hora X en una Fecha X; este alumno puede ingresar N Asistencias a esta misma tabla, siempre y cuando no sean el mismo día. Hasta aquí todo bien.

Ahora, para poder generar un reporte, necesito convertir las filas de este alumno X a columnas segun el rango de fechas solicitadas. No tuve problema en generar la siguiente consulta:

------------------------------

Select NOMBRE, [2016-03-23] as '2016-03-23', [2016-03-24] as '2016-03-24', [2016-03-25] as '2016-03-25'
from(Select NOMBRE,FECHA,HORA from Asistencias)
pvt
pivot(min(HORA) for [FECHA] in ([2016-03-23],[2016-03-24],[2016-03-25])) as Asistir
order by NOMBRE

------------------------------

Obteniendo el siguiente resultado:
NOMBRE 2016-03-23 2016-03-24 2016-03-25
Alumno X 17:02:00 17:00:00 17:06:00
Alumno Y 17:01:00 17:03:00 NULL
Alumno Z 17:05:00 17:04:00 17:08:00


El resultadol es excelente, ya que es justo lo que necesitaba, que aparezca un reporte tomando un alumno por fila y que las fechas ingresadas se convirtieran en columnas, logrando asi registrar a detalle la hora en la que cada alumno tomó o no tomó asistencia (null).

**** Problema ****

Ahora que he obtenido lo que quiero, deseo convertir esta consulta en un PROCEDIMIENTO DINAMICO y que a éste NO se le ingresen manualmente los valores de las N fechas, sino que se ingresen solo dos valores (fechaInicial y fechaFinal ) y que estos dos valores generen las columnas necesarias en dicho reporte (algo como un arreglo).

Es decir, si el Procedimiento recibe:

fechaInicial = '01/01/2016'

fechaFinal = '10/01/2016'

Entonces, se deberán generar 10 Columnas cada una con la fecha correspondiente.

Solo deseo que mi procedimiento sea dinámico y que sea capaz de recibir las dos fechas (inicial y final) y generar así las columnas necesarias segun el rango de estas fechas.

Espero haber sido lo suficientemente claro. Agradezco a todos su atención