Oracle
 sql >> Database >  >> RDS >> Oracle

La seguente query è possibile con SQL Pivot?

Ci è voluto un po' per rispondere, ma ho dovuto scrivere tutto questo e testarlo!

Dati con cui ho lavorato:

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 , così com'è ora, non consente un numero dinamico di colonne in modo semplice. Lo consente solo con la parola chiave XML, risultando in una colonna xmltype. Ecco alcuni documenti eccellenti. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Conviene sempre leggerli prima.

Come fare, allora?
Troverai letteralmente tonnellate di domande sulla stessa cosa una volta che inizi a cercare.

SQL dinamico

Un report classico può accettare un corpo di funzione che restituisce un'istruzione sql come ritorno. Un rapporto interattivo non può. Allo stato attuale, un IR è fuori questione in quanto troppo dipendente dai metadati.

Ad esempio, con queste query/plsql in un'origine dell'area del rapporto classica:

perno statico

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

Dichiarazione di restituzione del corpo della funzione

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;

Prendere nota tuttavia delle impostazioni nella sorgente della regione.

  • Utilizza nomi di colonna specifici della query e convalida query

Questa è l'impostazione standard. Analizzerà la tua query e quindi memorizzerà le colonne trovate nella query nei metadati del rapporto. Se vai avanti e crei un rapporto con il codice plsql sopra, puoi vedere che apex ha analizzato la query e ha assegnato le colonne corrette. Ciò che non va in questo approccio è che i metadati sono statici. I metadati del rapporto non vengono aggiornati ogni volta che il rapporto viene eseguito.
Ciò può essere dimostrato semplicemente aggiungendo un'altra classe ai dati.

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;

Esegui la pagina senza modificare il report! La modifica e il salvataggio rigenereranno i metadati, che chiaramente non è un metodo praticabile. I dati cambieranno comunque e non potrai accedere e salvare i metadati del rapporto ogni volta.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Utilizza nomi di colonna generici (interrogazione di analisi solo in fase di esecuzione)

L'impostazione della sorgente su questo tipo ti consentirà di utilizzare un approccio più dinamico. Modificando le impostazioni del report su questo tipo di analisi, apex genererà solo una quantità di colonne nei suoi metadati senza essere direttamente associato alla query effettiva. Ci saranno solo colonne con 'COL1', 'COL2', 'COL3',...
Esegui il rapporto. Funziona bene. Ora inserisci di nuovo alcuni dati.

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;

Esegui il rapporto. Funziona bene.
Tuttavia, il problema qui sono i nomi delle colonne. Non sono proprio così dinamici, con i loro brutti nomi. Puoi modificare le colonne, sicuramente, ma non sono dinamiche. Non viene visualizzata alcuna classe o altro, né puoi impostare in modo affidabile le loro intestazioni su una. Anche in questo caso ha senso:i metadati sono presenti, ma sono statici. Potrebbe funzionare per te se sei soddisfatto di questo approccio.
Puoi comunque affrontarlo. Negli "Attributi del rapporto" del rapporto, puoi selezionare un "Tipo di intestazioni". Sono tutti statici, aspettati ovviamente "PL/SQL"! Qui puoi scrivere un corpo di funzione (o semplicemente chiamare una funzione) che restituirà le intestazioni di colonna!

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;

Soluzione di terze parti

Usa XML

Io stesso ho scelto di utilizzare la parola chiave XML prima. Uso pivot per assicurarmi di avere valori per tutte le righe e le colonne, quindi lo leggo di nuovo con XMLTABLE e quindi creando un XMLTYPE colonna, serializzandola in un CLOB .
Questo può essere un po' avanzato, ma è una tecnica che ho usato un paio di volte finora, con buoni risultati. È veloce, a condizione che i dati di base non siano troppo grandi ed è solo una chiamata sql, quindi non molti cambi di contesto. L'ho usato anche con i dati di CUBE e funziona benissimo.
(nota:le classi che ho aggiunto agli elementi corrispondono alle classi usate sui report classici nel tema 1, rosso semplice)

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;

In APEX: bene, poiché l'HTML è stato costruito, questa può essere solo una regione PLSQL che chiama la funzione del pacchetto e la stampa usando HTP.PRN .

(modifica) C'è anche questo post sul forum OTN che fa lo stesso in gran parte, ma non genera intestazioni ecc., ma usa piuttosto le funzionalità apex:OTN:rapporto matrice

PLSQL

In alternativa, puoi semplicemente scegliere di seguire la buona strada di plsql. Puoi prendere il corpo dallo sql dinamico sopra, passarci sopra e creare una struttura di tabella usando htp.prn chiamate. Metti fuori le intestazioni e metti fuori tutto quello che vuoi. Per ottenere buoni risultati, aggiungi classi sugli elementi che corrispondono al tema che stai utilizzando.