Oracle
 sql >> Base de données >  >> RDS >> Oracle

La requête suivante est-elle possible avec SQL Pivot ?

J'ai mis du temps à répondre, mais j'ai dû écrire tout ça et le tester !

Données avec lesquelles j'ai travaillé :

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 , tel qu'il se présente actuellement, n'autorise pas un nombre dynamique de colonnes de manière simple. Cela ne le permet qu'avec le mot-clé XML, ce qui donne une colonne xmltype.Voici quelques excellents documents. http://www.oracle-base .com/articles/11g/pivot-and-unpivot-operators-11gr1.php
Cela vaut toujours la peine de les lire en premier.

Comment faire, alors ?
Vous trouverez littéralement des tonnes de questions sur la même chose une fois que vous aurez commencé la recherche.

SQL dynamique

Un rapport classique peut prendre un corps de fonction renvoyant une instruction SQL comme retour. Un rapport interactif ne peut pas. Dans l'état actuel des choses, un IR est hors de question car il dépend trop des métadonnées.

Par exemple, avec ces requêtes/plsql dans une source de région de rapport classique :

pivot statique

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

instruction de retour du corps de la fonction

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;

Prenez note cependant des paramètres dans la source de la région.

  • Utiliser des noms de colonne spécifiques à la requête et valider la requête

C'est le réglage standard. Il analysera votre requête, puis stockera les colonnes trouvées dans la requête dans les métadonnées du rapport. Si vous continuez et créez un rapport avec le code plsql ci-dessus, vous pouvez voir qu'apex a analysé la requête et a attribué les bonnes colonnes. Ce qui ne va pas avec cette approche, c'est que ces métadonnées sont statiques. Les métadonnées du rapport ne sont pas actualisées à chaque exécution du rapport.
Cela peut être prouvé tout simplement en ajoutant une autre classe aux données.

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;

Exécutez la page sans modifier le rapport ! L'édition et l'enregistrement régénéreront les métadonnées, ce qui n'est clairement pas une méthode viable. Les données changeront de toute façon et vous ne pourrez pas accéder et enregistrer les métadonnées du rapport à chaque fois.

--cleanup
begin
delete from class where id = 3;
delete from class_meeting where id = 5;
delete from meeting_attendance where id = 10;
end;
  • Utiliser des noms de colonne génériques (requête d'analyse lors de l'exécution uniquement)

Définir la source sur ce type vous permettra d'utiliser une approche plus dynamique. En modifiant les paramètres du rapport pour ce type d'analyse, apex générera simplement un certain nombre de colonnes dans ses métadonnées sans être directement associé à la requête réelle. Il n'y aura que des colonnes avec 'COL1', 'COL2', 'COL3',...
Exécutez le rapport. Fonctionne bien. Maintenant, insérez à nouveau des données.

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;

Exécutez le rapport. Fonctionne bien.
Cependant, le problème ici est le nom des colonnes. Ils ne sont pas vraiment très dynamiques, avec leurs vilains noms. Vous pouvez modifier les colonnes, bien sûr, mais elles ne sont pas dynamiques. Aucune classe n'est affichée ou quoi que ce soit, et vous ne pouvez pas définir de manière fiable leurs en-têtes sur un. Encore une fois, cela a du sens :les métadonnées sont là, mais elles sont statiques. Cela pourrait fonctionner pour vous si vous êtes satisfait de cette approche.
Vous pouvez cependant vous en occuper. Dans les "Attributs du rapport" du rapport, vous pouvez sélectionner un "Type de rubriques". Ils sont tous statiques, sauf pour "PL/SQL" bien sûr ! Ici, vous pouvez écrire un corps de fonction (ou simplement appeler une fonction) qui renverra les en-têtes de colonne !

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;

Solution tierce

Utiliser XML

J'ai moi-même choisi d'utiliser le mot-clé XML auparavant. J'utilise pivot pour m'assurer que j'ai des valeurs pour toutes les lignes et colonnes, puis je les relis avec XMLTABLE , puis en créant un XMLTYPE colonne, en la sérialisant en un CLOB .
C'est peut-être un peu avancé, mais c'est une technique que j'ai utilisée plusieurs fois jusqu'à présent, avec de bons résultats. C'est rapide, à condition que les données de base ne soient pas trop volumineuses, et qu'il s'agisse d'un seul appel sql, donc pas beaucoup de changements de contexte. Je l'ai également utilisé avec des données CUBE, et cela fonctionne très bien.
(note :les classes que j'ai ajoutées sur les éléments correspondent aux classes utilisées sur les rapports classiques dans le thème 1, rouge simple)

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;

Dans APEX : eh bien, puisque le HTML a été construit, cela ne peut être qu'une région PLSQL qui appelle la fonction de package et l'imprime en utilisant HTP.PRN .

(edit) Il y a aussi ce post sur le forum OTN qui fait la même chose en grande partie, mais ne génère pas de titres etc, mais utilise plutôt les fonctionnalités apex :OTN :rapport matriciel

PLSQL

Alternativement, vous pouvez simplement opter pour la bonne vieille route plsql. Vous pouvez prendre le corps du sql dynamique ci-dessus, boucler dessus et créer une structure de table en utilisant htp.prn appels. Mettez des en-têtes et mettez tout ce que vous voulez. Pour un bon effet, ajoutez des classes sur les éléments qui correspondent au thème que vous utilisez.