& # 8220; Pivoting & # 8221; una tabla en SQL (es decir, tabulación cruzada / tabla de contingencia)

StackOverflow https://stackoverflow.com/questions/213476

  •  03-07-2019
  •  | 
  •  

Pregunta

Estoy trabajando para intentar generar un informe a partir de un par de tablas de base de datos. La versión simplificada se ve así

Campaign
----------
CampaignID

Source
-----------------------
Source_ID | Campaign_ID

Content
---------------------------------------------------------
Content_ID | Campaign_ID | Content_Row_ID | Content_Value

El informe debe leerse así:

CampaignID - SourceID - ContentRowID(Value(A)) - ContentRowID(Value(B))

Donde ContentRowID (Valor (A)) significa " Buscar una fila tiene un CampaignID dado y un ContentRowId de " A " y luego obtenga el ContentValue para esa fila "

Esencialmente, tengo que " pivote " (Creo que ese es el término correcto) las filas en columnas ...

Es una base de datos Oracle 10g ...

¿Alguna sugerencia?

¿Fue útil?

Solución

Esta es mi primera puñalada. El refinamiento se realizará una vez que sepa más sobre el contenido de la tabla de Contenido.

Primero, necesitas una tabla temporal:

CREATE TABLE pivot (count integer);
INSERT INTO pivot VALUES (1);
INSERT INTO pivot VALUES (2);

Ahora estamos listos para realizar consultas.

SELECT campaignid, sourceid, a.contentvalue, b.contentvalue
FROM content a, content b, pivot, source
WHERE source.campaignid = content.campaignid
AND pivot = 1 AND a.contentrowid = 'A'
AND pivot = 2 AND b.contentrowid = 'B'

Otros consejos

Bill Karwin menciona esto, pero creo que merece ser señalado muy claramente:

SQL no hace lo que está pidiendo, por lo que cualquier " solución " lo que obtendrás será un kludge.

Si usted sabe , seguro, siempre se ejecutará en un Oracle 10, y luego, claro, la tabla de contingencia de Walter Mitty podría hacerlo. La forma correcta de hacerlo es trabajar la combinación más fácil de orden de clasificación en la consulta y el código de la aplicación para diseñarlo correctamente.

  • Funciona en otros sistemas de bases de datos,
  • no se arriesga a que ninguna otra capa cague (recuerdo que MySQL tiene un problema con las columnas > 255, por ejemplo. ¿Está seguro de que la biblioteca de interfaces se enfrenta, además de la propia db?)
  • (por lo general) no es mucho más difícil.

Si lo necesita, puede solicitar primero el Content_Row_ID , luego pregunte por las filas que necesite, ordenadas por CampaignID , ContentRowID , que le daría cada celda (poblada) en orden de izquierda a derecha, línea por línea.


Sal.

Hay un montón de cosas que el hombre moderno cree que SQL debería tener / hacer que simplemente no está allí. Este es uno, los rangos generados son otro, cierre recursivo, ORDER BY paramétrico, lenguaje de programación estandarizado ... y la lista continúa. (aunque, hay que admitirlo, hay un truco para ORDER BY )

Si no tiene un número dinámico de columnas y su conjunto de datos no es demasiado grande, podría hacer esto ...

SELECT CampaignID, SourceID, 
   (SELECT Content_Value FROM Content c 
      WHERE c.Campaign_ID=s.Campaign_ID 
      AND Content_Row_ID = 39100 
      AND rownum<=1) AS Value39100,
   (SELECT Content_Value FROM Content c 
      WHERE c.Campaign_ID=s.Campaign_ID 
      AND Content_Row_ID = 39200 
      AND rownum<=1) AS Value39200
FROM Source s;

Repita la subconsulta para cada Content_Row_ID adicional.

Para hacer esto en SQL estándar, necesita conocer todos los valores distintos de Content_Row_ID y hacer una unión por valor distinto. Entonces necesita una columna por valor distinto de Content_Row_ID.

SELECT CA.Campaign_ID, 
  C1.Content_Value AS "39100",
  C2.Content_Value AS "39200",
  C3.Content_Value AS "39300"
FROM Campaign CA
  LEFT OUTER JOIN Content C1 ON (CA.Campaign_ID = C1.Campaign_ID 
    AND C1.Content_Row_ID = 39100)
  LEFT OUTER JOIN Content C2 ON (CA.Campaign_ID = C2.Campaign_ID 
    AND C2.Content_Row_ID = 39200)
  LEFT OUTER JOIN Content C3 ON (CA.Campaign_ID = C3.Campaign_ID 
    AND C3.Content_Row_ID = 39300);

A medida que aumenta el número de valores distintos, esta consulta resulta demasiado costosa para ejecutarse de manera eficiente. Probablemente sea más fácil obtener los datos de manera más simple y reformatearlos en PL / SQL o en el código de la aplicación.

Bill Karwin y Anders Eurenius están en lo cierto al afirmar que no hay una solución que sea directa, ni que haya ninguna solución cuando el número de valores de columnas resultantes no se conoce de antemano. Oracle 11g lo simplifica un poco con el operador PIVOT , pero las columnas aún deben conocerse de antemano y eso no cumple con los criterios de 10 g de su pregunta.

Si necesita un número dinámico de columnas, no creo que esto pueda hacerse en SQL estándar, lo que, por desgracia, supera mi conocimiento. Pero hay características de Oracle que pueden hacerlo. Encontré algunos recursos:

http://www.sqlsnippets.com/en/topic-12200.html

http: //asktom.oracle.com/pls/asktom/f?p=100:11:0:::P11_QUESTION_ID:124812348063#41097616566309

Si tiene " Oracle, la referencia completa " busque una sección titulada, " Volviendo una tabla en su lado " ;. Esto proporciona ejemplos detallados e instrucciones para realizar un pivote, aunque la edición que tengo no lo llama un pivote.

Otro término para " girar una tabla " Es crosstabulation.

Una de las herramientas más fáciles de usar para realizar la tabla de contingencia es MS Access. Si tiene MS Access y puede establecer un enlace de tabla desde una base de datos de Access a su tabla de origen, ya está a mitad de camino.

En ese momento, puede activar el " Asistente de consultas " ;, y pedirle que cree una consulta de referencias cruzadas para usted. Realmente es tan fácil como responder las preguntas que le hace el asistente. El lado desafortunado de esta solución es que si observa la consulta resultante en la vista de SQL, verá un SQL que es peculiar al dialecto de acceso de SQL y no se puede usar, en general, en otras plataformas.

También puede descargar algunas herramientas de análisis simples del sitio web de Oracle y usar una de esas herramientas para realizar una tabla de contingencia para usted.

Una vez más, si realmente quieres hacerlo en SQL, " Oracle, la referencia completa " debería ayudarte.

Si no conoce la cantidad de columnas al frente, solo devuelva una consulta de SQL normal y use el código del lado del servidor como el que aparece aquí: Filling Datagrid And Sql Query

Hice una solución con este SQL. Necesitaba que las filas fueran el número de clases y que las columnas fueran el resumen de cada clasificación por mes, por lo tanto, la primera columna es el resumen de la fila y cada columna de ohters es el resumen de cada mes, y la última fila es el resumen de la columna completa mes a mes.

Buena suerte

Select DS.Cla,
Sum(case
when (Extract(year from DS.Data) =:intYear) then DS.PRE
else 0
end) as ToTal,
Sum(case
when (Extract(month from DS.Data) =1) then DS.PRE
else 0
end) as Jan,
Sum(case
when (Extract(month from DS.Data) =2) then DS.PRE
else 0
end) as FEV,
Sum(case
when (Extract(month from DS.Data) =3) then DS.PRE
else 0
end) as MAR,
Sum(case
when (Extract(month from DS.Data) =4) then DS.PRE
else 0
end) as ABR,
Sum(case
when (Extract(month from DS.Data) =5) then DS.PRE
else 0
end) as MAI,
Sum(case
when (Extract(month from DS.Data) =6) then DS.PRE
else 0
end) as JUN,
Sum(case
when (Extract(month from DS.Data) =7) then DS.PRE
else 0
end) as JUL,
Sum(case
when (Extract(month from DS.Data) =8) then DS.PRE
else 0
end) as AGO,
Sum(case
when (Extract(month from DS.Data) =9) then DS.PRE
else 0
end) as SETE,
Sum(case
when (Extract(month from DS.Data) =10) then DS.PRE
else 0
end) as OUT,
Sum(case
when (Extract(month from DS.Data) =11) then DS.PRE
else 0
end) as NOV,
Sum(case
when (Extract(month from DS.Data) =12) then DS.PRE
else 0
end) as DEZ
from Dados DS
Where DS.Cla > 0
And Extract(Year from DS.Data) = :intYear
group by DS.CLA

Union All

Select 0*count(DS.cla),  0*count(DS.cla),
Sum(case
when (Extract(month from DS.Data) =1) then DS.PRE
else 0
end) as JAN,
Sum(case
when (Extract(month from DS.Data) =2) then DS.PRE
else 0
end) as FEV,
Sum(case
when (Extract(month from DS.Data) =3) then DS.PRE
else 0
end) as MAR,
Sum(case
when (Extract(month from DS.Data) =4) then DS.PRE
else 0
end) as ABR,
Sum(case
when (Extract(month from DS.Data) =5) then DS.PRE
else 0
end) as MAI,
Sum(case
when (Extract(month from DS.Data) =6) then DS.PRE
else 0
end) as JUN,
Sum(case
when (Extract(month from DS.Data) =7) then DS.PRE
else 0
end) as JUL,
Sum(case
when (Extract(month from DS.Data) =8) then DS.PRE
else 0
end) as AGO,
Sum(case
when (Extract(month from DS.Data) =9) then DS.PRE
else 0
end) as SETE,
Sum(case
when (Extract(month from DS.Data) =10) then DS.PRE
else 0
end) as OUT,
Sum(case
when (Extract(month from DS.Data) =11) then DS.PRE
else 0
end) as NOV,
Sum(case
when (Extract(month from DS.Data) =12) then DS.PRE
else 0
end) as DEZ
from Dados DS
Where DS.Cla > 0
And Extract(Year from DS.Data) = :intYear
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top