sqloracle-databaseplsqlpivotdynamic-pivot

Use pivot for dynamically changing column headers using sql in oracle


I have a requirement where i need to pivot the data. However the pivot needs to be dynamic as the columns header would keep changing as per column app_id. So if app_id=1. The column header would be A,B,C,D, If app_id=2, column would be CDEF and so on. Also each set of value has an id. So for id, 120 and app_id=1 , column A,B,C,D, would display the values and so on.

The current sample data has only 2 app_ids, but there could be many more, so app_id and labels would kepe changing thus i need to write a dynamic query.

Sample table Data:

ID  label   value   app_id
--- -----   -----   ------
120 A       Alpha   1
120 B       Beta    1
120 C       Class   1
120 D       Delta   1
120 C       Alpha   2
120 D       Beta    2
120 E       Class   2
120 F       Delta   2

and the constructive query is

WITH data( ID, label, value, app_id ) AS
(
  SELECT 120, 'A', 'Alpha', 1 FROM dual UNION ALL
  SELECT 120, 'B', 'Beta' , 1 FROM dual UNION ALL
  SELECT 120, 'C', 'Class', 1 FROM dual UNION ALL
  SELECT 120, 'D', 'Delta', 1 FROM dual UNION ALL
  SELECT 120, 'C', 'Alpha', 2 FROM dual UNION ALL
  SELECT 120, 'D', 'Beta' , 2 FROM dual UNION ALL
  SELECT 120, 'E', 'Class', 2 FROM dual UNION ALL
  SELECT 120, 'F', 'Delta', 2 FROM dual  
)
SELECT * 
  FROM data
ID LABEL VALUE APP_ID
120 A Alpha 1
120 B Beta 1
120 C Class 1
120 D Delta 1
120 C Alpha 2
120 D Beta 2
120 E Class 2
120 F Delta 2
121 P Code 1
121 Q Beta 1
121 R Delph 1
121 S Sine 1
121 Q Code 2
121 R Beta 2
121 P Delph 2
121 S Sine 2

Expected output:

select * from data where id =120 and app_id=1;
APP_ID A B C D ID
1 Alpha Beta Class Delta 120
select * from data where id =120 and app_id=2;
APP_ID C D E F ID
2 Alpha Beta Class Delta 120
select * from data where id =121 and app_id=1;
APP_ID P Q R S ID
1 Code Beta Delph Sine 121
select * from data where id =121 and app_id=2;
APP_ID Q R P S ID
2 Code Beta Delph Sine 121

Solution

  • What you can do

    SELECT * 
      FROM data  
     PIVOT  
     (
      MAX(value) FOR label IN ('A' AS "A", 'B' AS "B",'C' AS "C",'D' AS "D")
     )
     WHERE ID = 120 AND app_id = 1
    

    as a static pivot statement might be converted to a function which contains two respective parameters

    CREATE OR REPLACE FUNCTION Get_Pivoted_Labels( i_id data.id%type, i_app_id data.app_id%type ) 
    RETURN SYS_REFCURSOR IS
      v_recordset SYS_REFCURSOR;
      v_sql       VARCHAR2(32767); 
      v_cols      VARCHAR2(32767);  
    BEGIN
      SELECT LISTAGG( ''''||label||''' AS "'||label||'"' , ',' )
              WITHIN GROUP ( ORDER BY label ) 
        INTO v_cols
        FROM ( SELECT DISTINCT label 
                 FROM data
                WHERE ID = i_id AND app_id = i_app_id );
    
      v_sql :=
          'SELECT * 
             FROM data
            PIVOT 
            (
              MAX(value) FOR label IN ( '|| v_cols ||' )
            )
           WHERE ID = :id AND app_id = :aid'; 
    
      OPEN v_recordset FOR v_sql USING i_id, i_app_id;
      RETURN v_recordset;
    END;
    /
    

    in which an auxiliary query, in which the label columns are distinctly selected, is used to determine the string(v_cols for 'A' AS "A", 'B' AS "B",'C' AS "C",'D' AS "D") to be concatenated to the main SQL string in order to be used within the cursor which returns a value of type SYS_REFCURSOR.

    and is invoked by

    VAR rc REFCURSOR
    VAR v_id NUMBER
    VAR v_app_id NUMBER
    EXEC :rc := Get_Pivoted_Labels(:v_id,:v_app_id);
    PRINT rc
    

    from SQL developer's console.

    Demonstration with generated SQL statements

    If order of columns in the SELECT list matters, then use the code below in order to create the function

    CREATE OR REPLACE FUNCTION Get_Pivoted_Labels( i_id data.id%type, i_app_id data.app_id%type ) 
    RETURN SYS_REFCURSOR IS
      v_recordset SYS_REFCURSOR;
      v_sql       VARCHAR2(32767); 
      v_cols_1    VARCHAR2(32767);    
      v_cols_2    VARCHAR2(32767);   
    BEGIN
      SELECT LISTAGG( ''''||label||''' AS "'||label||'"' , ',' )
              WITHIN GROUP ( ORDER BY label ),
             LISTAGG( label , ',' )
              WITHIN GROUP ( ORDER BY label )  
        INTO v_cols_1, v_cols_2
        FROM ( SELECT DISTINCT label, value 
                 FROM data
                WHERE ID = i_id AND app_id = i_app_id );
    
      v_sql :=
          'SELECT ID, '|| v_cols_2 ||', app_id
             FROM data
            PIVOT 
            (
              MAX(value) FOR label IN ( '|| v_cols_1 ||' )
            )
           WHERE ID = :id AND app_id = :aid'; 
    
      OPEN v_recordset FOR v_sql USING i_id, i_app_id;
      RETURN v_recordset;
    END;
    /