Odpowiedź zajęła trochę czasu, ale musiałem to wszystko napisać i przetestować!
Dane, z którymi pracowałem:
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 w obecnej formie nie pozwala na dynamiczną liczbę kolumn w prosty sposób. Pozwala to tylko za pomocą słowa kluczowego XML, co skutkuje kolumną xmltype. Oto kilka doskonałych dokumentów. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Zawsze warto przeczytać je najpierw.
Jak to zrobić?
Po rozpoczęciu wyszukiwania znajdziesz dosłownie mnóstwo pytań na ten sam temat.
Dynamiczny SQL
- https:/ /asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4471013000346257238
- Dynamiczne obracanie tabeli Oracle
- Dynamiczny Oracle Pivot_In_Clause
Klasyczny raport może przyjąć treść funkcji zwracającą instrukcję sql jako return. Interaktywny raport nie może. W obecnej sytuacji IR nie wchodzi w rachubę, ponieważ jest zbyt zależna od metadanych.
Na przykład z tymi zapytaniami/plsql w klasycznym źródle regionu raportu:
statyczna oś obrotu
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
Oświadczenie zwracające treść funkcji
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;
Zwróć jednak uwagę na ustawienia w źródle regionu.
- Użyj nazw kolumn specyficznych dla zapytania i weryfikuj zapytanie
To jest ustawienie standardowe. Przeanalizuje zapytanie, a następnie przechowa kolumny znalezione w zapytaniu w metadanych raportu. Jeśli pójdziesz dalej i utworzysz raport z powyższym kodem plsql, zobaczysz, że apex przeanalizował zapytanie i przypisał prawidłowe kolumny. Błędem tego podejścia jest to, że metadane są statyczne. Metadane raportu nie są odświeżane za każdym razem, gdy raport jest uruchamiany.
Można to udowodnić w prosty sposób, dodając kolejną klasę do danych.
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;
Uruchom stronę bez edytowania raportu! Edytowanie i zapisywanie spowoduje ponowne wygenerowanie metadanych, co oczywiście nie jest opłacalną metodą. Dane i tak ulegną zmianie i nie będzie można za każdym razem wchodzić i zapisywać metadanych raportu.
--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
- Użyj ogólnych nazw kolumn (analizuj zapytanie tylko w czasie wykonywania)
Ustawienie źródła na ten typ pozwoli na zastosowanie bardziej dynamicznego podejścia. Zmieniając ustawienia raportu na ten typ analizowania, apex po prostu wygeneruje pewną liczbę kolumn w swoich metadanych bez bezpośredniego powiązania z rzeczywistym zapytaniem. Będą tylko kolumny z „COL1”, „COL2”, „COL3”...
Uruchom raport. Działa w porządku. Teraz wstaw ponownie trochę danych.
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;
Uruchom raport. Działa dobrze.
Jednak zagięciem są tutaj nazwy kolumn. Tak naprawdę nie są aż tak dynamiczni, ze swoimi brzydkimi imionami. Oczywiście możesz edytować kolumny, ale nie są one dynamiczne. Nie ma wyświetlanej klasy ani niczego, ani nie można wiarygodnie ustawić ich nagłówków na jeden. Znowu ma to sens:metadane tam są, ale są statyczne. To może działać dla Ciebie, jeśli jesteś zadowolony z takiego podejścia.
Możesz jednak sobie z tym poradzić. W sekcji „Atrybuty raportu” raportu możesz wybrać „Typ nagłówka”. Wszystkie są statyczne, spodziewaj się oczywiście "PL/SQL"! Tutaj możesz napisać treść funkcji (lub po prostu wywołać funkcję), która zwróci nagłówki kolumn!
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;
Rozwiązanie innej firmy
- 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/
W APEX: chociaż dynamiczny obrót jest prostszy po instalacji, konfiguracja w apex pozostaje taka sama, jak w przypadku użycia dynamicznego SQL. Użyj klasycznego raportu z ogólnymi nazwami kolumn.
Nie będę tu wchodzić w szczegóły. Nie mam zainstalowanego tego pakietu atm. Fajnie jest mieć, ale w tym scenariuszu może to nie być pomocne. Pozwala tylko na napisanie dynamicznego obrotu w bardziej zwięzły sposób, ale nie pomaga zbytnio po stronie wierzchołków. Jak pokazałem powyżej, dynamiczne kolumny i statyczne metadane raportów wierzchołkowych są tutaj czynnikiem ograniczającym.
Użyj XML
Sam zdecydowałem się wcześniej użyć słowa kluczowego XML. Używam pivota, aby upewnić się, że mam wartości dla wszystkich wierszy i kolumn, a następnie odczytuję je ponownie za pomocą XMLTABLE
, a następnie utworzenie jednego XMLTYPE
kolumna, serializując ją do CLOB
.
Może to trochę zaawansowana technika, ale jest to technika, której używałem do tej pory kilka razy, z dobrymi wynikami. Jest szybki, pod warunkiem, że dane bazowe nie są zbyt duże i jest to tylko jedno wywołanie sql, więc nie ma wielu zmian kontekstu. Używałem go również z danymi CUBE i działa świetnie.
(Uwaga:klasy, które dodałem do elementów odpowiadają klasom używanym w klasycznych raportach w motywie 1, prosty czerwony)
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;
W APEX: cóż, ponieważ HTML został skonstruowany, może to być tylko region PLSQL, który wywołuje funkcję pakietu i wyświetla ją przy użyciu HTP.PRN
.
(edytuj) Jest też ten post na forum OTN, który w dużej mierze robi to samo, ale nie generuje nagłówków itp., Zamiast tego używa funkcji wierzchołka:OTN:raport macierzy
PLSQL
Alternatywnie możesz po prostu wybrać dobrą, starą trasę plsql. Możesz pobrać ciało z dynamicznego sql powyżej, zapętlić je i wypuścić strukturę tabeli za pomocą htp.prn
wzywa. Opublikuj nagłówki i wypuść cokolwiek jeszcze chcesz. Aby uzyskać dobry efekt, dodaj klasy na elementach, które odpowiadają motywowi, którego używasz.