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

Er følgende forespørgsel mulig med SQL Pivot?

Det tog et stykke tid at svare, men jeg var nødt til at skrive det hele op og teste det!

Data jeg har arbejdet med:

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 , som det står lige nu, tillader ikke et dynamisk antal kolonner på en enkel måde. Det tillader kun dette med XML-nøgleordet, hvilket resulterer i en xmltype-kolonne. Her er nogle fremragende dokumenter. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Det kan altid betale sig at læse dem først.

Hvordan gør man så?
Du vil bogstaveligt talt finde tonsvis af spørgsmål om det samme, når du begynder at søge.

Dynamisk SQL

En klassisk rapport kan tage en funktionstekst, der returnerer en sql-sætning, som retur. En interaktiv rapport kan ikke. Som det står, er en IR udelukket, da den er for metadataafhængig.

For eksempel med disse forespørgsler/plsql i en klassisk rapportområdekilde:

statisk pivot

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

funktionstekst returnerende erklæring

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;

Vær dog opmærksom på indstillingerne i regionskilden.

  • Brug forespørgselsspecifikke kolonnenavne og valider forespørgsel

Dette er standardindstillingen. Det vil analysere din forespørgsel og derefter gemme kolonnerne i forespørgslen i rapportens metadata. Hvis du går videre og opretter en rapport med ovenstående plsql-kode, kan du se, at apex har parset forespørgslen og har tildelt de korrekte kolonner. Hvad der er galt med denne tilgang er, at de metadata er statiske. Rapportens metadata opdateres ikke, hver gang rapporten køres.
Dette kan bevises ganske enkelt ved at tilføje en anden klasse til dataene.

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;

Kør siden uden at redigere rapporten! Redigering og lagring vil regenerere metadataene, hvilket tydeligvis ikke er en holdbar metode. Dataene ændres alligevel, og du kan ikke gå ind og gemme rapportens metadata hver gang.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Brug generiske kolonnenavne (kun parse forespørgsel ved kørsel)

Indstilling af kilden til denne type vil give dig mulighed for at bruge en mere dynamisk tilgang. Ved at ændre indstillingerne for rapporten til denne type parsing, vil apex blot generere en mængde kolonner i dens metadata uden at være direkte forbundet med den faktiske forespørgsel. Der vil kun være kolonner med 'COL1', 'COL2', 'COL3',...
Kør rapporten. Fungerer fint. Indsæt nu nogle data igen.

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;

Kør rapporten. Fungerer fint.
Knikken her er dog kolonnenavnene. De er egentlig ikke så dynamiske med deres grimme navne. Du kan selvfølgelig redigere kolonnerne, men de er ikke dynamiske. Der er ingen klasse, der vises eller noget, og du kan heller ikke pålideligt indstille deres overskrifter til en. Igen giver det mening:Metadataene er der, men de er statiske. Det kunne fungere for dig, hvis du er tilfreds med denne tilgang.
Du kan dog håndtere dette. I rapportens "Rapportattributter" kan du vælge en "Overskriftstype". De er alle statiske, forvent selvfølgelig "PL/SQL"! Her kan du skrive en funktionstekst (eller bare kalde en funktion), som returnerer kolonneoverskrifterne!

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;

Tredjepartsløsning

Brug XML

Jeg har selv valgt at bruge XML søgeordet før. Jeg bruger pivot for at sikre, at jeg har værdier for alle rækker og kolonner, og læs det derefter op igen med XMLTABLE , og derefter oprette en XMLTYPE kolonne, serialiserer den til en CLOB .
Dette er måske lidt avanceret, men det er en teknik, jeg har brugt et par gange indtil videre, med gode resultater. Det er hurtigt, forudsat at basisdataene ikke er for store, og det kun er et sql-kald, så der er ikke mange kontekstskift. Jeg har også brugt det med CUBE'd-data, og det fungerer godt.
(bemærk:de klasser, jeg har tilføjet på elementerne, svarer til klasser, der bruges på klassiske rapporter i tema 1, simpel rød)

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;

I APEX: godt, da HTML'en er blevet konstrueret, kan dette kun være en PLSQL-region, som kalder pakkefunktionen og udskriver den ved hjælp af HTP.PRN .

(rediger) Der er også dette indlæg på OTN-forummet, som gør det samme for en stor del, men ikke genererer overskrifter osv., snarere ved at bruge apex-funktionaliteterne:OTN:Matrix-rapport

PLSQL

Alternativt kan du bare vælge at gå den gode gamle plsql-rute. Du kan tage brødteksten fra den dynamiske sql ovenfor, sløjfe over den og udsende en tabelstruktur ved at bruge htp.prn opkald. Sæt overskrifter ud, og læg hvad du ellers vil have. For god effekt, tilføje klasser på de elementer, der svarer til det tema, du bruger.



  1. Forespørgsel om flere tabeller i Oracle SQL plus 11g

  2. Hvordan binder man brugere til forskellige organisationer, universiteter, virksomheder med forskellige roller?

  3. Hvordan sletter man rækker med tovejsafhængigheder?

  4. MySQL foreach loop