Oracle
 sql >> база данни >  >> RDS >> Oracle

Възможна ли е следната заявка с SQL Pivot?

Отне ми известно време, за да отговоря, но трябваше да напиша всичко това и да го тествам!

Данни, с които съм работил:

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, както е сега, не позволява динамичен брой колони по прост начин. Той позволява това само с ключовата дума XML, което води до колона xmltype. Ето някои отлични документи. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Винаги си струва първо да ги прочетете.

Как тогава?
Буквално ще намерите тонове въпроси за едно и също нещо, след като започнете да търсите.

Динамичен SQL

Класическият отчет може да приеме тяло на функция, връщащо sql оператор като return. Интерактивен отчет не може. В сегашния си вид IR не може да става и дума, тъй като е твърде зависим от метаданни.

Например с тези заявки/plsql в класически източник на регион на отчет:

статичен ос

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

изявление, връщащо тялото на функция

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;

Обърнете внимание обаче на настройките в регионалния източник.

  • Използвайте имена на колони, специфични за заявката, и валидирайте заявката

Това е стандартната настройка. Той ще анализира вашата заявка и след това ще съхрани колоните, открити в заявката, в метаданните на отчета. Ако продължите и създадете отчет с горния plsql код, можете да видите, че apex е анализирал заявката и е присвоил правилните колони. Това, което не е наред с този подход, е, че метаданните са статични. Метаданните на отчета не се опресняват всеки път, когато отчетът се изпълнява.
Това може да се докаже съвсем просто чрез добавяне на друг клас към данните.

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;

Стартирайте страницата без да редактирате отчета! Редактирането и запазването ще регенерира метаданните, което очевидно не е жизнеспособен метод. Данните така или иначе ще се променят и не можете да влизате и да записвате метаданните на отчета всеки път.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Използване на общи имена на колони (разбор на заявка само по време на изпълнение)

Задаването на източника на този тип ще ви позволи да използвате по-динамичен подход. Чрез промяна на настройките на отчета към този тип синтактичен анализ, apex просто ще генерира количество колони в своите метаданни, без да се свързва директно с действителната заявка. Ще има само колони с „COL1“, „COL2“, „COL3“,...
Изпълнете отчета. Работи добре. Сега вмъкнете отново някои данни.

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;

Пуснете отчета. Работи добре.
Въпреки това, пречупването тук са имената на колоните. Те всъщност не са толкова динамични с грозните си имена. Можете да редактирате колоните, разбира се, но те не са динамични. Няма показан клас или нещо подобно, нито можете надеждно да зададете техните заглавки на едно. Отново това има смисъл:метаданните са там, но са статични. Може да работи за вас, ако сте доволни от този подход.
Все пак можете да се справите с това. В „Атрибути на отчета“ на отчета можете да изберете „Тип заглавия“. Всички те са статични, очаквайте "PL/SQL", разбира се! Тук можете да напишете тяло на функция (или просто да извикате функция), която ще върне заглавките на колоните!

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;

Решение на трета страна

  • 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/
    В APEX: въпреки че динамичният пивот е по-прост след инсталирането, настройката в apex остава същата, както ако искате да използвате динамичен SQL. Използвайте класически отчет с общи имена на колони.
    Няма да навлизам в много подробности тук. Нямам този пакет инсталиран atm. Хубаво е да го има, но в този сценарий може да не е толкова полезно. Той просто ви позволява да напишете динамичен пивот по по-сбит начин, но не помага много от върховата страна на нещата. Както демонстрирах по-горе, динамичните колони и статичните метаданни на отчетите за върха са ограничаващият фактор тук.

Използване на XML

Аз самият съм избрал да използвам ключовата дума XML преди. Използвам завъртане, за да се уверя, че имам стойности за всички редове и колони, след което го прочитам отново с XMLTABLE и след това създаване на един XMLTYPE колона, като я сериализира в CLOB .
Това може да е малко напреднало, но това е техника, която съм използвал няколко пъти досега, с добри резултати. Бързо е, при условие че базовите данни не са твърде големи и това е само едно sql извикване, така че няма много контекстни превключвания. Използвал съм го и с данни от CUBE и работи страхотно.
(забележка:класовете, които добавих към елементите, съответстват на класовете, използвани в класически отчети в тема 1, просто червено)

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;

В APEX: добре, тъй като HTML е конструиран, това може да бъде само PLSQL регион, който извиква пакетната функция и я отпечатва с помощта на HTP.PRN .

(редактиране) Има и тази публикация във форума на OTN, която прави същото в голяма част, но не генерира заглавия и т.н., а по-скоро използва функциите на apex:OTN:Матричен отчет

PLSQL

Като алтернатива можете просто да изберете добрия стар plsql път. Можете да вземете тялото от динамичния sql по-горе, да преминете през него и да изведете структура на таблица, като използвате htp.prn обаждания. Поставете заглавки и поставете каквото искате. За добър ефект добавете класове към елементите, които отговарят на темата, която използвате.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. грешка при липсваща ключова дума в оператора на oracle CASE WHEN sql

  2. Не е грешка в израза GROUP BY

  3. Как да отпечатам триъгълник от звезди с помощта на SQL

  4. Как да се справя с callableStatement.registerOutParameter(1, java.sql.Types.BOOLEAN);

  5. Извличане и групово събиране от REF CURSOR, върнат от процедура