Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

A consulta a seguir é possível com o SQL Pivot?


Demorou um pouco para responder, mas tive que escrever tudo isso e testar!

Dados com os quais trabalhei:
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 , como está agora, não permite um número dinâmico de colunas de maneira simples. Ele só permite isso com a palavra-chave XML, resultando em uma coluna xmltype. Aqui estão alguns documentos excelentes. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Sempre vale a pena lê-los primeiro.

Como, então?
Você encontrará literalmente toneladas de perguntas sobre a mesma coisa quando começar a pesquisar.

SQL Dinâmico


Um relatório clássico pode receber um corpo de função retornando uma instrução sql como retorno. Um relatório interativo não pode. Tal como está, um IR está fora de questão, pois é muito dependente de metadados.

Por exemplo, com essas consultas/plsql em uma origem de região de relatório clássica:

pivô estático
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

instrução de retorno do corpo da função
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;

Observe, no entanto, as configurações na origem da região.
  • Use nomes de coluna específicos de consulta e valide a consulta

Esta é a configuração padrão. Ele analisará sua consulta e armazenará as colunas encontradas na consulta nos metadados do relatório. Se você prosseguir e criar um relatório com o código plsql acima, poderá ver que o apex analisou a consulta e atribuiu as colunas corretas. O que há de errado com essa abordagem é que esses metadados são estáticos. Os metadados do relatório não são atualizados toda vez que o relatório está sendo executado.
Isso pode ser comprovado simplesmente adicionando outra classe aos dados.
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;

Execute a página sem editar o relatório! Editar e salvar irá regenerar os metadados, o que claramente não é um método viável. Os dados serão alterados de qualquer maneira, e você não pode entrar e salvar os metadados do relatório todas as vezes.
--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Usar nomes de coluna genéricos (consulta de análise apenas em tempo de execução)

Definir a origem para esse tipo permitirá que você use uma abordagem mais dinâmica. Ao alterar as configurações do relatório para esse tipo de análise, o apex gerará apenas uma quantidade de colunas em seus metadados sem estar diretamente associado à consulta real. Haverá apenas colunas com 'COL1', 'COL2', 'COL3',...
Execute o relatório. Funciona bem. Agora insira alguns dados novamente.
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;

Execute o relatório. Funciona bem.
No entanto, o problema aqui são os nomes das colunas. Eles não são realmente tão dinâmicos, com seus nomes feios. Você pode editar as colunas, com certeza, mas elas não são dinâmicas. Não há nenhuma classe sendo exibida nem nada, nem você pode definir seus cabeçalhos de forma confiável para um. Novamente, isso faz sentido:os metadados estão lá, mas são estáticos. Pode funcionar para você se você estiver satisfeito com essa abordagem.
Você pode, no entanto, lidar com isso. Nos "Atributos do Relatório" do relatório, você pode selecionar um "Tipo de Títulos". Eles são todos estáticos, exceto por "PL/SQL" é claro! Aqui você pode escrever um corpo de função (ou apenas chamar uma função) que retornará os cabeçalhos das colunas!
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;

Solução de terceiros

Usar XML


Eu mesmo optei por usar a palavra-chave XML antes. Eu uso pivot para ter certeza de que tenho valores para todas as linhas e colunas, então leio novamente com XMLTABLE e, em seguida, criando um XMLTYPE coluna, serializando-a para um CLOB .
Isso pode ser um pouco avançado, mas é uma técnica que usei algumas vezes até agora, com bons resultados. É rápido, desde que os dados de base não sejam muito grandes e seja apenas uma chamada sql, portanto, não há muitas trocas de contexto. Eu usei com dados CUBE'd também, e funciona muito bem.
(nota:as classes que adicionei nos elementos correspondem às classes usadas em relatórios clássicos no tema 1, vermelho simples)
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;

No APEX: bem, desde que o HTML foi construído, isso só pode ser uma região PLSQL que chama a função de pacote e a imprime usando HTP.PRN .

(editar) Há também este post no fórum da OTN que faz o mesmo em grande parte, mas não gera títulos etc., usando as funcionalidades do apex:OTN:relatório de matriz

PLSQL


Alternativamente, você pode optar por seguir a boa e velha rota do plsql. Você pode pegar o corpo do sql dinâmico acima, fazer um loop sobre ele e colocar uma estrutura de tabela usando htp.prn chamadas. Coloque cabeçalhos e publique o que mais você quiser. Para um bom efeito, adicione classes nos elementos que correspondem ao tema que você está usando.