Det tog ett tag att svara, men jag var tvungen att skriva upp det hela och testa det!
Data jag har arbetat 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 ser ut just nu, tillåter inte ett dynamiskt antal kolumner på ett enkelt sätt. Det tillåter endast detta med XML-nyckelordet, vilket resulterar i en xmltype-kolumn. Här är några utmärkta dokument. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Det lönar sig alltid att läsa dem först.
Hur gör man då?
Du kommer bokstavligen att hitta massor av frågor om samma sak när du börjar söka.
Dynamisk SQL
- https:/ /asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4471013000346257238
- Dynamiskt svänga ett bord Oracle
- Dynamisk Oracle Pivot_In_Clause
En klassisk rapport kan ta en funktionskropp som returnerar en sql-sats som retur. En interaktiv rapport kan inte. Som det ser ut är en IR utesluten eftersom den är för metadataberoende.
Till exempel med dessa queries/plsql i en klassisk rapportregionkälla:
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
funktionstext som returnerar text
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;
Notera dock inställningarna i regionkällan.
- Använd frågespecifika kolumnnamn och validera sökfråga
Detta är standardinställningen. Den analyserar din fråga och lagrar sedan kolumnerna som finns i frågan i rapportens metadata. Om du går vidare och skapar en rapport med ovanstående plsql-kod kan du se att apex har analyserat frågan och har tilldelat rätt kolumner. Det som är fel med detta tillvägagångssätt är att den metadatan är statisk. Rapportens metadata uppdateras inte varje gång rapporten körs.
Detta kan bevisas helt enkelt genom att lägga till ytterligare en klass till data.
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 sidan utan att redigera rapporten! Redigering och sparande kommer att återskapa metadata, vilket uppenbarligen inte är en hållbar metod. Datan kommer att ändras hur som helst, och du kan inte gå in och spara rapportens metadata varje gång.
--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
- Använd allmänna kolumnnamn (endast analysera fråga vid körning)
Om du ställer in källan till den här typen kan du använda ett mer dynamiskt tillvägagångssätt. Genom att ändra inställningarna för rapporten till denna typ av analys, kommer apex bara att generera ett antal kolumner i sin metadata utan att vara direkt associerad med den faktiska frågan. Det kommer bara att finnas kolumner med 'COL1', 'COL2', 'COL3',...
Kör rapporten. Fungerar bra. Infoga nu lite 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. Fungerar bra.
Men knäcket här är kolumnnamnen. De är inte riktigt så dynamiska, med sina fula namn. Du kan redigera kolumnerna, men de är inte dynamiska. Det finns ingen klass som visas eller något, inte heller kan du på ett tillförlitligt sätt ställa in deras rubriker till en. Återigen är detta vettigt:metadatan finns där, men den är statisk. Det kan fungera för dig om du är nöjd med detta tillvägagångssätt.
Du kan dock hantera detta. I rapportens "Rapportattribut" kan du välja en "Rubriktyp". De är alla statiska, förvänta dig för "PL/SQL" förstås! Här kan du skriva en funktionskropp (eller bara anropa en funktion) som returnerar kolumnrubrikerna!
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
- https3 ://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/
I APEX: även om den dynamiska pivoten är enklare efter installation, förblir inställningen i apex densamma som om du skulle vilja använda dynamisk SQL. Använd en klassisk rapport med generiska kolumnnamn.
Jag ska inte gå in på så mycket detaljer här. Jag har inte det här paketet installerat atm. Det är trevligt att ha, men i det här scenariot kanske det inte är så användbart. Det låter dig helt enkelt skriva en dynamisk pivot på ett mer kortfattat sätt, men det hjälper inte mycket på spetssidan av saker och ting. Som jag har visat ovan är de dynamiska kolumnerna och den statiska metadatan i apexrapporterna den begränsande faktorn här.
Använd XML
Jag har själv valt att använda sökordet XML tidigare. Jag använder pivot för att se till att jag har värden för alla rader och kolumner och läser sedan upp det igen med XMLTABLE
, och skapa sedan en XMLTYPE
kolumn, serialisera den till en CLOB
.
Det här kan vara lite avancerat, men det är en teknik som jag har använt ett par gånger hittills, med bra resultat. Det är snabbt, förutsatt att basdatan inte är för stor, och det bara är ett sql-anrop, så inte många kontextväxlar. Jag har använt det med CUBE'd-data också, och det fungerar utmärkt.
(notera:klasserna jag har lagt till på elementen överensstämmer med klasser som används i klassiska rapporter i tema 1, enkel 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: Tja, eftersom HTML har konstruerats kan detta bara vara en PLSQL-region som anropar paketfunktionen och skriver ut den med HTP.PRN
.
(redigera) Det finns också det här inlägget på OTN-forumet som gör detsamma till stor del, men som inte genererar rubriker etc, snarare använder apex-funktionerna:OTN:Matrisrapport
PLSQL
Alternativt kan du bara välja att gå den bra gamla plsql-vägen. Du kan ta kroppen från den dynamiska sql ovan, loopa över den och lägga ut en tabellstruktur genom att använda htp.prn
samtal. Lägg ut rubriker och lägg ut vad du vill. För god effekt, lägg till klasser på de element som motsvarar det tema du använder.