sql >> Base de Datos >  >> RDS >> Oracle

¿Es posible la siguiente consulta con SQL Pivot?

Me tomó un tiempo responder, ¡pero tenía que escribir todo esto y probarlo!

Datos con los que he trabajado:

begin 
insert into student(id, name) values (1, 'Tom');
insert into student(id, name) values (2, 'Odysseas');
insert into class(id, subject) values (1, 'Programming');
insert into class(id, subject) values (2, 'Databases');
insert into class_meeting (id, class_id, meeting_sequence) values (1, 1, 10);
insert into class_meeting (id, class_id, meeting_sequence) values (2, 1, 20);
insert into class_meeting (id, class_id, meeting_sequence) values (3, 2, 10);
insert into class_meeting (id, class_id, meeting_sequence) values (4, 2, 20);
insert into meeting_attendance (id, student_id, meeting_id, present) values (1, 1, 1, 1); -- Tom was at meeting 10 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (2, 1, 2, 1); -- Tom was at meeting 20 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (3, 1, 3, 0); -- Tom was NOT at meeting 10 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (4, 1, 4, 0); -- Tom was NOT at meeting 20 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (5, 2, 1, 0); -- Odysseas was NOT at meeting 10 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (6, 2, 2, 1); -- Odysseas was at meeting 20 about programming
insert into meeting_attendance (id, student_id, meeting_id, present) values (7, 2, 3, 0); -- Odysseas was NOT at meeting 10 about databases
insert into meeting_attendance (id, student_id, meeting_id, present) values (8, 2, 4, 1); -- Odysseas was at meeting 20 about databases
end;

PIVOT, tal como está ahora, no permite un número dinámico de columnas de una manera simple. Solo permite esto con la palabra clave XML, lo que da como resultado una columna de tipo xml. Aquí hay algunos documentos excelentes. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Siempre vale la pena leerlos primero.

¿Cómo, entonces?
Literalmente encontrarás toneladas de preguntas sobre el mismo tema una vez que comiences a buscar.

SQL dinámico

Un informe clásico puede tomar el cuerpo de una función que devuelve una declaración SQL como devolución. Un informe interactivo no puede. Tal como está, un IR está fuera de discusión ya que depende demasiado de los metadatos.

Por ejemplo, con estas consultas/plsql en un origen de región de informe clásico:

pivote estático

select *
from (
select s.name as student_name, m.present present, cm.meeting_sequence||'-'|| c.subject meeting
from student s
join meeting_attendance m
on s.id = m.student_id
join class_meeting cm
on cm.id = m.meeting_id
join class c
on c.id = cm.class_id
)
pivot ( max(present) for meeting in ('10-Databases' as "10-DB", '20-Databases' as "20-DB", '10-Programming' as "10-PRM", '20-Programming' as "20-PRM") );

-- Results
STUDENT_NAME '10-Databases' 20-DB 10-PRM 20-PRM
Tom          0              0     1      1
Odysseas     0              1     0      1

declaración de retorno del cuerpo de la función

DECLARE
  l_pivot_cols VARCHAR2(4000);
  l_pivot_qry VARCHAR2(4000);
BEGIN
  SELECT ''''||listagg(cm.meeting_sequence||'-'||c.subject, ''',''') within group(order by 1)||''''
    INTO l_pivot_cols
    FROM class_meeting cm
    JOIN "CLASS" c
      ON c.id = cm.class_id;

  l_pivot_qry := 
        'select * from ( '
     || 'select s.name as student_name, m.present present, cm.meeting_sequence||''-''||c.subject meeting '
     || 'from student s '
     || 'join meeting_attendance m '
     || 'on s.id = m.student_id '
     || 'join class_meeting cm '
     || 'on cm.id = m.meeting_id '
     || 'join class c '
     || 'on c.id = cm.class_id '
     || ') '
     || 'pivot ( max(present) for meeting in ('||l_pivot_cols||') )' ;

  RETURN l_pivot_qry;
END;

Sin embargo, tome nota de la configuración en la fuente de la región.

  • Usar nombres de columna específicos de consulta y validar consulta

Esta es la configuración estándar. Analizará su consulta y luego almacenará las columnas encontradas en la consulta en los metadatos del informe. Si continúa y crea un informe con el código plsql anterior, puede ver que Apex analizó la consulta y asignó las columnas correctas. Lo que está mal con este enfoque es que los metadatos son estáticos. Los metadatos del informe no se actualizan cada vez que se ejecuta el informe.
Esto se puede probar simplemente agregando otra clase a los datos.

begin
insert into class(id, subject) values (3, 'Watch YouTube');
insert into class_meeting (id, class_id, meeting_sequence) values (5, 3, 10);
insert into meeting_attendance (id, student_id, meeting_id, present) values (10, 1, 5, 1); -- Tom was at meeting 10 about watching youtube
end;

¡Ejecute la página sin editar el informe! Editar y guardar regenerará los metadatos, lo que claramente no es un método viable. Los datos cambiarán de todos modos, y no puede ingresar y guardar los metadatos del informe cada vez.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Usar nombres de columna genéricos (analizar consulta solo en tiempo de ejecución)

Establecer la fuente en este tipo le permitirá utilizar un enfoque más dinámico. Al cambiar la configuración del informe a este tipo de análisis, Apex solo generará una cantidad de columnas en sus metadatos sin estar directamente asociado con la consulta real. Solo habrá columnas con 'COL1', 'COL2', 'COL3',...
Ejecute el informe. Funciona bien. Ahora inserte algunos datos nuevamente.

begin
insert into class(id, subject) values (3, 'Watch YouTube');
insert into class_meeting (id, class_id, meeting_sequence) values (5, 3, 10);
insert into meeting_attendance (id, student_id, meeting_id, present) values (10, 1, 5, 1); -- Tom was at meeting 10 about watching youtube
end;

Ejecute el informe. Funciona bien.
Sin embargo, el problema aquí son los nombres de las columnas. No son realmente tan dinámicos, con sus feos nombres. Puede editar las columnas, seguramente, pero no son dinámicas. No se muestra ninguna clase ni nada, ni puede establecer de manera confiable sus encabezados en uno. Una vez más, esto tiene sentido:los metadatos están ahí, pero son estáticos. Podría funcionar para usted si está satisfecho con este enfoque.
Sin embargo, puede lidiar con esto. En los "Atributos del informe" del informe, puede seleccionar un "Tipo de encabezado". ¡Todos son estáticos, excepto "PL/SQL" por supuesto! ¡Aquí puede escribir el cuerpo de una función (o simplemente llamar a una función) que devolverá los encabezados de las columnas!

DECLARE
  l_return VARCHAR2(400);
BEGIN
  SELECT listagg(cm.meeting_sequence||'-'||c.subject, ':') within group(order by 1)
    INTO l_return
    FROM class_meeting cm
    JOIN "CLASS" c
      ON c.id = cm.class_id;

  RETURN l_return;
END;

Solución de terceros

    https ://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:4843682300346852395#5394721000346803830
  • https://stackoverflow.com/a/16702401/814048
  • http://technology .amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/
    En APEX: aunque el pivote dinámico es más sencillo después de la instalación, la configuración en Apex sigue siendo la misma que si quisiera usar SQL dinámico. Utilice un informe clásico con nombres de columna genéricos.
    No voy a entrar en muchos detalles aquí. No tengo este paquete instalado atm. Es bueno tenerlo, pero en este escenario puede no ser tan útil. Simplemente le permite escribir un pivote dinámico de una manera más concisa, pero no ayuda mucho en el lado superior de las cosas. Como he demostrado anteriormente, las columnas dinámicas y los metadatos estáticos de los informes de Apex son el factor limitante aquí.

Usar XML

Yo mismo he optado por usar la palabra clave XML antes. Uso pivote para asegurarme de tener valores para todas las filas y columnas, luego lo leo de nuevo con XMLTABLE y luego crear un XMLTYPE columna, serializándola a un CLOB .
Esto puede ser un poco avanzado, pero es una técnica que he usado un par de veces hasta ahora, con buenos resultados. Es rápido, siempre que la base de datos no sea demasiado grande, y es solo una llamada sql, por lo que no hay muchos cambios de contexto. También lo he usado con datos de CUBE y funciona muy bien.
(nota:las clases que he agregado en los elementos se corresponden con las clases utilizadas en los informes clásicos en el tema 1, rojo simple)

DECLARE
  l_return CLOB;
BEGIN
  -- Subqueries:
  -- SRC
  -- source data query
  -- SRC_PIVOT
  -- pivoted source data with XML clause to allow variable columns. 
  -- Mainly used for convenience because pivot fills in 'gaps' in the data.
  -- an example would be that 'Odysseas' does not have a relevant record for the 'Watch Youtube' class
  -- PIVOT_HTML
  -- Pulls the data from the pivot xml into columns again, and collates the data
  -- together with xmlelments.
  -- HTML_HEADERS
  -- Creates a row with just header elements based on the source data
  -- HTML_SRC
  -- Creates row elements with the student name and the collated data from pivot_html
  -- Finally:
  -- serializes the xmltype column for easier-on-the-eye markup
  WITH src AS (
    SELECT s.name as student_name, m.present present, cm.meeting_sequence||'-'||c.subject meeting
      FROM student s
      JOIN meeting_attendance m
        ON s.id = m.student_id
      JOIN class_meeting cm
        ON cm.id = m.meeting_id
      JOIN class c
        ON c.id = cm.class_id 
  ),
  src_pivot AS (
  SELECT student_name, meeting_xml
    FROM src pivot xml(MAX(NVL(present, 0)) AS is_present_max for (meeting) IN (SELECT distinct meeting FROM src) )
  ),
  pivot_html AS (
  SELECT student_name
       , xmlagg(
           xmlelement("td", xmlattributes('data' as "class"), is_present_max)
           ORDER BY meeting
         ) is_present_html
    FROM src_pivot
       , xmltable('PivotSet/item'
           passing meeting_xml
           COLUMNS "MEETING" VARCHAR2(400) PATH 'column[@name="MEETING"]'
                 , "IS_PRESENT_MAX" NUMBER  PATH 'column[@name="IS_PRESENT_MAX"]')
   GROUP BY (student_name)
  ),
  html_headers AS (
  SELECT xmlelement("tr", 
          xmlelement("th", xmlattributes('header' as "class"), 'Student Name')
        , xmlagg(xmlelement("th", xmlattributes('header' as "class"), meeting) order by meeting) 
        ) headers
    FROM (SELECT DISTINCT meeting FROM src)
  ),
  html_src as (
  SELECT 
    xmlagg(
      xmlelement("tr", 
          xmlelement("td", xmlattributes('data' as "class"), student_name)
        , ah.is_present_html
      )
    ) data
    FROM pivot_html ah
  )
  SELECT 
    xmlserialize( content 
      xmlelement("table"
        , xmlattributes('report-standard' as "class", '0' as "cellpadding", '0' as "cellspacing", '0' as "border")
        , xmlelement("thead", headers )
        , xmlelement("tbody", data )
      )
      AS CLOB INDENT SIZE = 2
    )
    INTO l_return
    FROM html_headers, html_src ;

  htp.prn(l_return);
END;

En APEX: bueno, dado que se ha construido el HTML, solo puede ser una región PLSQL que llama a la función del paquete y la imprime usando HTP.PRN .

(editar) También hay esta publicación en el foro de OTN que hace lo mismo en gran parte, pero no genera encabezados, etc., sino que usa las funcionalidades de Apex:OTN:Informe de matriz

PLSQL

Alternativamente, puede optar por seguir la buena ruta plsql. Puede tomar el cuerpo del sql dinámico anterior, recorrerlo y generar una estructura de tabla usando htp.prn llamadas Publique encabezados y publique cualquier otra cosa que desee. Para un buen efecto, agregue clases en los elementos que se correspondan con el tema que está utilizando.