Die Antwort hat eine Weile gedauert, aber ich musste das alles aufschreiben und testen!
Daten, mit denen ich gearbeitet habe:
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 erlaubt in seiner jetzigen Form keine dynamische Anzahl von Spalten auf einfache Weise. Es erlaubt dies nur mit dem XML-Schlüsselwort, was zu einer xmltype-Spalte führt. Hier sind einige ausgezeichnete Dokumente. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Es lohnt sich immer, diese zuerst zu lesen.
Wie also?
Sie werden buchstäblich jede Menge Fragen zum selben Thema finden, sobald Sie mit der Suche beginnen.
Dynamisches SQL
- https:/ /asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4471013000346257238
- Dynamisches Schwenken eines Tabellen-Orakels
- Dynamisches Oracle Pivot_In_Clause
Ein klassischer Bericht kann einen Funktionskörper annehmen, der eine SQL-Anweisung als Rückgabe zurückgibt. Ein interaktiver Bericht kann dies nicht. So wie es aussieht, kommt eine IR nicht in Frage, da sie zu metadatenabhängig ist.
Beispielsweise mit diesen Abfragen/plsql in einer klassischen Berichtsregionsquelle:
statischer Drehpunkt
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
Funktionskörper-Rückgabeanweisung
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;
Beachten Sie jedoch die Einstellungen in der Region Quelle.
- Abfragespezifische Spaltennamen verwenden und Abfrage validieren
Dies ist die Standardeinstellung. Es analysiert Ihre Abfrage und speichert dann die in der Abfrage gefundenen Spalten in den Berichtsmetadaten. Wenn Sie fortfahren und einen Bericht mit dem obigen plsql-Code erstellen, können Sie sehen, dass Apex die Abfrage geparst und die richtigen Spalten zugewiesen hat. Was an diesem Ansatz falsch ist, ist, dass diese Metadaten statisch sind. Die Metadaten des Berichts werden nicht jedes Mal aktualisiert, wenn der Bericht ausgeführt wird.
Dies kann ganz einfach nachgewiesen werden, indem den Daten eine weitere Klasse hinzugefügt wird.
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;
Führen Sie die Seite aus, ohne den Bericht zu bearbeiten! Durch Bearbeiten und Speichern werden die Metadaten neu generiert, was eindeutig keine praktikable Methode ist. Die Daten ändern sich sowieso, und Sie können nicht jedes Mal hineingehen und die Berichtsmetadaten speichern.
--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
- Generische Spaltennamen verwenden (Abfrage nur zur Laufzeit parsen)
Wenn Sie die Quelle auf diesen Typ festlegen, können Sie einen dynamischeren Ansatz verwenden. Indem Sie die Einstellungen des Berichts auf diese Art der Analyse ändern, generiert Apex nur eine Reihe von Spalten in seinen Metadaten, ohne direkt mit der eigentlichen Abfrage verbunden zu sein. Es gibt nur Spalten mit 'COL1', 'COL2', 'COL3',...
Führen Sie den Bericht aus. Funktioniert gut. Fügen Sie nun wieder einige Daten ein.
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;
Führen Sie den Bericht aus. Funktioniert gut.
Der Haken hier sind jedoch die Spaltennamen. Sie sind nicht wirklich dynamisch, mit ihren hässlichen Namen. Sie können die Spalten natürlich bearbeiten, aber sie sind nicht dynamisch. Es wird keine Klasse angezeigt oder so, noch können Sie ihre Header zuverlässig auf eins setzen. Auch dies macht Sinn:Die Metadaten sind da, aber sie sind statisch. Es könnte für Sie funktionieren, wenn Sie mit diesem Ansatz zufrieden sind.
Sie können jedoch damit umgehen. In den „Berichtsattributen“ des Berichts können Sie einen „Überschriftentyp“ auswählen. Sie sind alle statisch, abgesehen natürlich von "PL/SQL"! Hier können Sie einen Funktionskörper schreiben (oder einfach eine Funktion aufrufen), der die Spaltenüberschriften zurückgibt!
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;
Drittanbieterlösung
- 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/
In APEX: Obwohl der dynamische Pivot nach der Installation einfacher ist, bleibt die Einrichtung in Apex die gleiche, als ob Sie dynamisches SQL verwenden möchten. Verwenden Sie einen klassischen Bericht mit generischen Spaltennamen.
Ich werde hier nicht zu sehr ins Detail gehen. Ich habe dieses Paket nicht installiert atm. Es ist schön zu haben, aber in diesem Szenario ist es möglicherweise nicht so hilfreich. Es erlaubt Ihnen lediglich, einen dynamischen Pivot präziser zu schreiben, hilft aber nicht viel auf der Scheitelseite der Dinge. Wie ich oben gezeigt habe, sind hier die dynamischen Spalten und die statischen Metadaten der Apex-Berichte der limitierende Faktor.
XML verwenden
Ich selbst habe mich schon einmal für das Schlüsselwort XML entschieden. Ich verwende Pivot, um sicherzustellen, dass ich Werte für alle Zeilen und Spalten habe, und lese sie dann mit XMLTABLE
erneut aus , und erstellen Sie dann einen XMLTYPE
-Spalte, die in ein CLOB
serialisiert wird .
Das mag etwas fortgeschritten sein, aber ich habe diese Technik bisher ein paar Mal mit guten Ergebnissen angewendet. Es ist schnell, vorausgesetzt, die Basisdaten sind nicht zu groß, und es ist nur ein SQL-Aufruf, also nicht viele Kontextwechsel. Ich habe es auch mit CUBE-Daten verwendet und es funktioniert großartig.
(Hinweis:Die Klassen, die ich den Elementen hinzugefügt habe, entsprechen den Klassen, die in klassischen Berichten in Thema 1 verwendet werden, einfach rot)
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: Nun, da das HTML konstruiert wurde, kann dies nur eine PLSQL-Region sein, die die Paketfunktion aufruft und sie mit HTP.PRN
ausgibt .
(Bearbeiten) Es gibt auch diesen Beitrag im OTN-Forum, der größtenteils dasselbe tut, aber keine Überschriften usw. generiert, sondern die Apex-Funktionalitäten verwendet:OTN:Matrixbericht
PLSQL
Alternativ können Sie sich einfach für die gute alte plsql-Route entscheiden. Sie könnten den Hauptteil aus dem dynamischen SQL oben nehmen, darüber schleifen und eine Tabellenstruktur ausgeben, indem Sie htp.prn
verwenden Anrufe. Veröffentlichen Sie Header und veröffentlichen Sie, was Sie sonst noch wollen. Fügen Sie für einen guten Effekt Klassen zu den Elementen hinzu, die dem von Ihnen verwendeten Thema entsprechen.