Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Czy następujące zapytanie jest możliwe z SQL Pivot?

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

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

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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zmiana tego zapytania na grupowanie wierszy i filtrowanie wszystkich wierszy oprócz tego o najmniejszej wartości

  2. Zapobieganie wstrzykiwaniu SQL za pomocą dynamicznego zapytania SQL ALTER USER

  3. problem z tworzeniem nagłówków przy użyciu bufora w sqlplus

  4. Nie można użyć dwóch łączy do bazy danych w jednym zapytaniu

  5. Jak dodać „ON DELETE CASCADE” w instrukcji ALTER TABLE?