Skip to Main Content
  • Questions
  • Accessing dynamically generated pivot columns through a cursor

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Devahnand.

Asked: February 05, 2018 - 7:32 pm UTC

Last updated: February 06, 2018 - 10:21 am UTC

Version: 11g

Viewed 10K+ times! This question is

You Asked

Hi Tom,

I have a PL/SQL report wherein the columns change dynamically (refer sample SQL: https://livesql.oracle.com/apex/livesql/s/f8gbm32e2acju7jzvuzjjh3a1 ) based on the parameter.
Suppose if the report is run for the Q1 2018, then it will have 3 columns (Jan, Feb and Mar) which are actually rows in the table.

I can use the pivot to get the output in the desired format but need to know how to access the dynamically changing column-names inside PL/SQL to show in the report output.

Thanks in advance for the solution.


with LiveSQL Test Case:

and Chris said...

You can't dynamically pass the list of values to pivot:

create table my_tab(acc varchar2(10), period varchar2(10), revenue number);

insert into my_tab values('100', 'jan-18', 100);
insert into my_tab values('100', 'feb-18', 200);
insert into my_tab values('100', 'mar-18', 300);
insert into my_tab values('101', 'jan-18', 150);
insert into my_tab values('101', 'feb-18', 250);
insert into my_tab values('101', 'mar-18', 350);
insert into my_tab values('101', 'apr-18', 350);
insert into my_tab values('101', 'may-18', 350);

commit;

var p1 number;
var p2 number;
var p3 number;

select *
from   my_tab pivot (
  sum(revenue) rev 
  for period in (
    :p1 p1, :p2 p2, :p3 p3
  ) 
);

SQL Error: ORA-56900: bind variable is not supported inside pivot|unpivot operation


So you need to go for some form of dynamic SQL anyway. In which case it doesn't really matter what you call them! Using open for, you fetch by position anyway. You just need to know how many columns you're returning.

If you know you'll always have three periods, you can select them into P1, P2, and P3 values or similar:

declare
  cur sys_refcursor;
  type period_res is record (
    acc    varchar2(10),
    p1_rev int,
    p2_rev int,
    p3_rev int
  );
  type period_res_arr is table of period_res index by pls_integer;
  results period_res_arr;
begin
  open cur for q'| select * 
from   my_tab pivot (
  sum(revenue) rev 
  /* replace in list with your dynamic generation */
  for period in ('jan-18' p1, 'feb-18' p2, 'mar-18' p3) 
) |';
  loop
     fetch cur bulk collect into results limit 100;
     exit when results.count = 0;
     
     for res in 1 .. results.count loop
       dbms_output.put_line (
         'Acc ' || results(res).acc || ' P1 ' || results(res).p1_rev || 
         ' P2 ' || results(res).p2_rev || ' P3 ' || results(res).p3_rev
       );
     end loop;
     
  end loop;
  close cur;
end;
/

Acc 101 P1 150 P2 250 P3 350
Acc 100 P1 100 P2 200 P3 300


...unless you're brave enough to play with XML that is! ;)

Here you can use a query to dynamically define the fields. But you get the results as XML. So you need to convert this:

set long 100000
select acc, xmlserialize(Document period_xml as varchar2(1000) indent size=2) 
from   my_tab pivot xml (
  sum(revenue) rev 
  for period in (
    select 'jan-18' p1 from dual union all
    select 'feb-18' p2 from dual union all
    select 'mar-18' p3 from dual 
  ) 
);

ACC   XMLSERIALIZE(DOCUMENTPERIOD_XMLASVARCHAR2(1000)INDENTSIZE=2)                                                                                                                                                                                                                                                                 
100   <PivotSet>
  <item>
    <column name="PERIOD">feb-18</column>
    <column name="REV">200</column>
  </item>
  <item>
    <column name="PERIOD">jan-18</column>
    <column name="REV">100</column>
  </item>
  <item>
    <column name="PERIOD">mar-18</column>
    <column name="REV">300</column>
  </item>
</PivotSet>
   
101   <PivotSet>
  <item>
    <column name="PERIOD">feb-18</column>
    <column name="REV">250</column>
  </item>
  <item>
    <column name="PERIOD">jan-18</column>
    <column name="REV">150</column>
  </item>
  <item>
    <column name="PERIOD">mar-18</column>
    <column name="REV">350</column>
  </item>
</PivotSet>


Again, extract the values into generic P1, P2, etc. variables.

Is this answer out of date? If it is, please let us know via a Comment

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.