sql >> Database >  >> RDS >> Oracle

Is de volgende query mogelijk met SQL Pivot?

Het duurde even voordat ik antwoord kreeg, maar ik moest dit allemaal opschrijven en testen!

Gegevens waarmee ik heb gewerkt:

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 staat, zoals het er nu voor staat, niet op een simpele manier een dynamisch aantal kolommen toe. Het staat dit alleen toe met het XML-sleutelwoord, wat resulteert in een xmltype-kolom. Hier zijn enkele uitstekende documenten. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Het loont altijd om die eerst te lezen.

Hoe dan?
Je zult letterlijk talloze vragen over hetzelfde vinden als je eenmaal begint te zoeken.

Dynamische SQL

Een klassiek rapport kan een functietekst hebben die een sql-instructie retourneert. Een interactief rapport kan dat niet. Zoals het er nu uitziet, is een IR uitgesloten omdat het te afhankelijk is van metadata.

Bijvoorbeeld met deze query's/plsql in een klassieke bron voor rapportregio's:

statische spil

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

function body return statement

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;

Let echter op de instellingen in de regiobron.

  • Gebruik queryspecifieke kolomnamen en valideer query

Dit is de standaard instelling. Uw query wordt geparseerd en vervolgens worden de kolommen in de query opgeslagen in de metagegevens van het rapport. Als u doorgaat en een rapport maakt met de bovenstaande plsql-code, kunt u zien dat apex de query heeft geparseerd en de juiste kolommen heeft toegewezen. Wat er mis is met deze aanpak is dat die metadata statisch is. De metagegevens van het rapport worden niet telkens vernieuwd wanneer het rapport wordt uitgevoerd.
Dit kan eenvoudig worden bewezen door een andere klasse aan de gegevens toe te voegen.

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;

Voer de pagina uit zonder het rapport te bewerken! Bewerken en opslaan zal de metadata opnieuw genereren, wat duidelijk geen haalbare methode is. De gegevens zullen hoe dan ook veranderen en u kunt niet elke keer naar binnen gaan en de metagegevens van het rapport opslaan.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Gebruik generieke kolomnamen (query alleen tijdens runtime ontleden)

Als u de bron op dit type instelt, kunt u een meer dynamische benadering gebruiken. Door de instellingen van het rapport te wijzigen in dit type parsing, genereert apex slechts een aantal kolommen in zijn metadata zonder direct te worden geassocieerd met de eigenlijke query. Er zullen alleen kolommen zijn met 'COL1', 'COL2', 'COL3',...
Voer het rapport uit. Werkt prima. Voer nu opnieuw wat gegevens in.

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;

Voer het rapport uit. Werkt prima.
De knik hier zijn echter de kolomnamen. Ze zijn niet echt zo dynamisch, met hun lelijke namen. U kunt de kolommen zeker bewerken, maar ze zijn niet dynamisch. Er wordt geen klasse weergegeven of zoiets, en je kunt hun headers ook niet betrouwbaar op één instellen. Nogmaals, dit is logisch:de metadata is er, maar het is statisch. Het kan voor u werken als u tevreden bent met deze aanpak.
U kunt dit echter wel aan. In de "Rapportattributen" van het rapport kunt u een "Type koptekst" selecteren. Ze zijn allemaal statisch, verwacht natuurlijk "PL/SQL"! Hier kun je een functietekst schrijven (of gewoon een functie aanroepen) die de kolomkoppen teruggeeft!

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;

Oplossing van derden

XML gebruiken

Zelf heb ik er al eerder voor gekozen om het XML-trefwoord te gebruiken. Ik gebruik pivot om ervoor te zorgen dat ik waarden heb voor alle rijen en kolommen, en lees het dan opnieuw voor met XMLTABLE en maak vervolgens een XMLTYPE kolom, serialiseren naar een CLOB .
Dit is misschien een beetje geavanceerd, maar het is een techniek die ik tot nu toe een paar keer heb gebruikt, met goede resultaten. Het is snel, op voorwaarde dat de basisgegevens niet te groot zijn, en het is slechts één sql-aanroep, dus niet veel contextwisselingen. Ik heb het ook met CUBE-gegevens gebruikt en het werkt geweldig.
(let op:de klassen die ik aan de elementen heb toegevoegd, komen overeen met klassen die worden gebruikt in klassieke rapporten in thema 1, simpel rood)

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: Nou, aangezien de HTML is geconstrueerd, kan dit alleen een PLSQL-regio zijn die de pakketfunctie aanroept en deze afdrukt met HTP.PRN .

(edit) Er is ook dit bericht op het OTN-forum dat grotendeels hetzelfde doet, maar geen koppen enz. genereert, maar eerder de apex-functionaliteiten gebruikt:OTN:Matrixrapport

PLSQL

Je kunt er ook voor kiezen om de goede oude plsql-route te volgen. Je zou de body van de dynamische sql hierboven kunnen nemen, eroverheen kunnen lussen en een tabelstructuur kunnen maken met behulp van htp.prn belt. Zet headers op, en zet uit wat je nog meer wilt. Voeg voor een goed effect klassen toe aan de elementen die overeenkomen met het thema dat u gebruikt.



  1. PHP stuur e-mail voor elke gebruiker

  2. MySQL:LAAD GEGEVENS LOKAAL INFILE inschakelen

  3. Is substr of LIKE sneller in Oracle?

  4. PostgreSQL vindt gemeenschappelijke combinatie van paren in dezelfde kolom