sql >> データベース >  >> RDS >> Oracle

次のクエリはSQLピボットで可能ですか?

    答えるのに少し時間がかかりましたが、これをすべて書き留めてテストする必要がありました!

    私が扱ったデータ:

    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 / plugin-and-unpivot-operators-11gr1.php
    最初にそれらを読むことは常に報われます。

    それでは、どうすればよいですか?
    検索を開始すると、同じことについて文字通りたくさんの質問が見つかります。

    動的SQL

    従来のレポートでは、SQLステートメントを返す関数本体をreturnとして受け取ることができます。インタラクティブレポートはできません。現状では、IRはメタデータに依存しすぎているため、問題外です。

    たとえば、従来のレポート領域ソースでこれらのquerys / 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;
    

    レポートを実行します。正常に動作します。
    ただし、ここでのねじれは列名です。醜い名前で、それほどダイナミックではありません。確かに列を編集することはできますが、動的ではありません。表示されているクラスなどはありません。また、ヘッダーを確実に1に設定することもできません。繰り返しますが、これは理にかなっています。メタデータはありますが、静的です。このアプローチに満足している場合は、うまくいく可能性があります。
    ただし、これに対処することはできます。レポートの「レポート属性」で、「見出しタイプ」を選択できます。これらはすべて静的です。もちろん「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;
    

    サードパーティソリューション

    XMLを使用する

    私自身、以前はXMLキーワードを使用することを選択しました。ピボットを使用してすべての行と列の値があることを確認してから、XMLTABLEで再度読み取ります。 、次に1つのXMLTYPEを作成します 列、CLOBにシリアル化します 。
    これは少し進んでいるかもしれませんが、これまでに2、3回使用した手法であり、良好な結果が得られています。基本データが大きすぎず、SQL呼び出しが1回だけなので、コンテキストスイッチがそれほど多くない場合は高速です。 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が作成されているので、これはパッケージ関数を呼び出してHTP.PRNを使用して出力するPLSQL領域のみになります。 。

    (編集)OTNフォーラムにもこの投稿があります。これは大部分が同じですが、見出しなどを生成せず、頂点機能を使用します: OTN:マトリックスレポート

    PLSQL

    または、古き良きplsqlルートを選択することもできます。上記の動的SQLから本体を取得し、それをループして、htp.prnを使用してテーブル構造を出力できます。 呼び出します。ヘッダーを出し、他に好きなものを出します。効果を上げるには、使用しているテーマに対応する要素にクラスを追加します。



    1. PostgreSQLはより多くの出力を無効にします

    2. ツリー構造を作成するために必要なPHP再帰ヘルプ

    3. ページネーションクエリの行の総数を取得する

    4. SQLSELECTステートメントの式の値を他の式に再利用する