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.
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.