Skip to Main Content
  • Questions
  • Dynamically vary the number of columns in a query

Breadcrumb

XMAS_TREE

The AskTOM team is taking a break over the holiday season, so we're not taking questions or responding to comments, but we might still sneak in a few Office Hours sessions! Thanks for being a member of the AskTOM community.

Question and Answer

Tom Kyte

Thanks for the question, Scott.

Asked: February 02, 2003 - 7:36 pm UTC

Last updated: July 01, 2011 - 8:15 am UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

G'day Tom

I have a question regarding layout of a query based on the data within a table.
Consider a simplified table as:
DATE
FILENAME
DATA_TYPE
DATA_COUNT

Which logs the number of records for each particular type of data in a filename.
eg:
FILE1 WO 3
FILE1 WT 5
FILE2 WO 1
...
The variable here could be the data_type (this actually presents a type of letter in our application, and new letter types could evolve).

If we had a query that displayed the following results:
DATE LOG WO WT Total
... z
Totals: x y

In my current understanding of how to query this data, there would be a static number of columns. Adding a new data_type would require modifying the query/report.

Is there a way to make this query dynamic enough to cater for an expanding amount of data_types?
I know it would be possible to use dynamic sql (for eg - the owa_sylk package you've demonstrated in this website), but I'm curious to know whether it could be done in a static SQL statement (varying number of columns)?

Thanks Tom.



and Tom said...

static sql by definition has a FIXED NUMBER of columns.

You can use some more "obscure" column types such as

o CURSOR types -- return a list of columns/row
o varray/nested table types -- returns an array or table of values/row


but if you want them to be columns -- you will be using dynamic sql. If you are interested, in my chapter on Analytics in "Expert one on one Oracle", I have a stored procedure there that does just this -- returning a ref cursor.

Rating

  (18 ratings)

We're not taking comments currently, so please try again later if you want to add a comment.

Comments

pivots?

Scott, February 03, 2003 - 7:05 pm UTC

G'day

After some further reading, would investigating pivot queries be of any use?
Could it apply in this case?

Thanks.

Tom Kyte
February 03, 2003 - 7:16 pm UTC

A pivot query would have to be dynamically written -- as the number of columns change

if the number of columns were fixed - a static pivot query would work just fine.

The example I suggested in my book dynamically writes a pivot query! You tell it the dimension to pivot on -- it finds the distinct values and creates a pivot query based on them

query

A reader, June 09, 2004 - 10:14 am UTC

hi tom,

can you please help me with the query.


CREATE TABLE TABL_RESULTDATA
(
Loadpoints VARCHAR2(125)
, LoadPointSteps VARCHAR2(125)
, Variable VARCHAR2(125)
, Step NUMBER
, Value NUMBER
, VariableType VARCHAR2(20)
)
/

insert into tabl_resultdata values('HA_000_LP_000','S_001', 'AD1Out(ADcard_1)',1,5, 'SET')
/
insert into tabl_resultdata values('HA_000_LP_000','S_001','N',1,100,'SET')
/
insert into tabl_resultdata values('HA_000_LP_000','S_001','Load(DDE)',1,1,'SET')
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','Q_quer(Emi2)',1,9.5,'MEAS')
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','T_I(Emi2)',1,0,'MEAS')
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','AD1Out(ADcard_1)',1,0,'SET')
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','N',1,100,'SET')
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','Load(DDE)',1,1,'SET')
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','Q_Ueber(KMM)',1,49.83,'MEAS')
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','T_Volkor(KMM)',1,0,'MEAS')
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','C1(LeCroy)',1,0,'MEAS')
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',1,10,'SET')
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',2,12,'SET')
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',3,12,'SET')
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1(ADcard_1)',1,9.025,'MEAS')
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1(ADcard_1)',2,9.05,'MEAS')
/

i want the result in such a way.

all distinct variables should be in horizontal columns.
all distinct loadpoints vertical.
loadpoints will repeat vetically if it has more than one loadpointsteps.
loadpoints will also repeat vetically if it has more than one steps.

then based on that each variable value should be represented as shown below.


AD1Out(ADcard_1) N Load(DDE) Q_quer(Emi2) T_I(Emi2) Q_Ueber(KMM) T_Volkor(KMM) C1(LeCroy) AD1(ADcard_1)

HA_000_LP_000 S_001 1 5 100 1
HA_000_LP_000 S_002 1 0 100 1 9.5 0
HA_000_LP_001 S_001 1 49 0 0
HA_000_LPSCHL_000 S_001 1 10 9.025
HA_000_LPSCHL_000 S_001 2 12 9.05
HA_000_LPSCHL_000 S_001 3 12


thanks .

Tom Kyte
June 09, 2004 - 10:15 am UTC

not everything can be done in a query. this looks like "a report" to me.


you'd have to run at least two queries -- one to get the column names (you need to get the distinct "variables" in order to even build the second query)




Thanks

A reader, June 09, 2004 - 11:07 am UTC

hi tom ,

from your site i found how to build the query dynamically and got the results.

create or replace package body pivot
as
procedure data( p_cursor in out rc )
is
l_stmt long;
l_index number := 0;
begin
l_stmt := 'select loadpoints, loadpointsteps ,step ';
for x in ( select distinct variable from tabl_resultdata order by 1 )
loop
l_index := l_index + 1;
-- l_stmt := l_stmt ||', max(decode(variable,' || ''''|| x.variable ||''''||', value )) ' ||replace(replace(x.variable, '('), ')');
l_stmt := l_stmt ||', max(decode(variable,' || ''''|| x.variable ||''''||', value )) VALUE_'||l_index ;
end loop;
l_stmt := l_stmt || ' from tabl_Resultdata group by loadpoints, loadpointsteps, step order by loadpoints';
open p_cursor for l_stmt;
end;
end;
/

column loadpoints format a17
column loadpointsteps format a5
column step format 999
column value_1 format 999.999
column value_2 format 999.999
column value_3 format 999.999
column value_4 format 999.999
column value_5 format 999.999
column value_6 format 999.999
column value_7 format 999.999
column value_8 format 999.999
column value_9 format 999.999


variable x refcursor
set autoprint on
exec pivot.data( :x );

but now if i have to loop through the ref cursor and write to a file instead of executing in the sql*plus how can i reference the column names from ref cursor becuase it is not known to me.


maybe i am making a silly mistake.
pls help me out.

Tom Kyte
June 09, 2004 - 11:39 am UTC

</code> http://asktom.oracle.com/~tkyte/flat/index.html <code>

get the dump_csv plsql routine from there. you cannot use a ref cursor, you can use dbms_sql.

Hide columns in pivot on need basis

Raghu, June 20, 2004 - 3:03 am UTC

hi tom,

thanks a lot. i could do it as told by you. i wrote exec_sql in forms and i am able to write the contents into excel and also the column names.

now i have a new requirement.

here is the testcase , it runs for me and hope u can also run this.

CREATE TABLE TABL_RESULTDATA
(
Loadpoints VARCHAR2(125)
, LoadPointSteps VARCHAR2(125)
, Variable VARCHAR2(125)
, Step NUMBER
, Value NUMBER
, VariableType VARCHAR2(20)
, ReadBackValue NUMBER
)
/

insert into tabl_resultdata values('HA_000_LP_000','S_001', 'AD1Out(ADcard_1)',1,5, 'SET', 10)
/
insert into tabl_resultdata values('HA_000_LP_000','S_001','N',1,100,'SET', 10)
/
insert into tabl_resultdata values('HA_000_LP_000','S_001','Load(DDE)',1,1,'SET', null)
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','Q_quer(Emi2)',1,9.5,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','T_I(Emi2)',1,0,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','AD1Out(ADcard_1)',1,0,'SET', null)
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','N',1,100,'SET', null)
/
insert into tabl_resultdata values('HA_000_LP_000','S_002','Load(DDE)',1,1,'SET', null)
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','Q_Ueber(KMM)',1,49.83,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','T_Volkor(KMM)',1,0,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LP_001','S_001','C1(LeCroy)',1,0,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',1,10,'SET', null)
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',2,12,'SET', 17)
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1Out(ADcard_1)',3,12,'SET', 19)
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1(ADcard_1)',1,9.025,'MEAS', null)
/
insert into tabl_resultdata values('HA_000_LPSCHL_000','S_001','AD1(ADcard_1)',2,9.05,'MEAS', null)
/


create or replace package pivot
as
type rc is ref cursor;
procedure data(p_cursor in out rc);
end;
/


create or replace package body pivot
as
procedure data( p_cursor in out rc )
is
l_stmt long;
l_index number := 0;
begin
l_stmt := 'select loadpoints, loadpointsteps ,step ';
for x in ( select distinct variable, variabletype from tabl_resultdata order by 2 )
loop
l_index := l_index + 1;
l_stmt := l_stmt ||', max(decode(variable,' || ''''|| x.variable||''''||', value )) ' ||replace(replace(x.variable||'('||x.variabletype||')', '('), ')');
end loop;
l_stmt := l_stmt || ' from tabl_Resultdata group by loadpoints,loadpointsteps, step order by loadpoints';
open p_cursor for l_stmt;
end;
end;
/

set linesize 198
column loadpoints format a17
column loadpointsteps format a5
column step format 999
column value_1 format 999.999
column value_2 format 999.999
column value_3 format 999.999
column value_4 format 999.999
column value_5 format 999.999
column value_6 format 999.999
column value_7 format 999.999
column value_8 format 999.999
column value_9 format 999.999


variable x refcursor
set autoprint on
exec pivot.data( :x );


By requirement is, the last column ReadBackValue is only applicable for variabletype='SET' and it is not applicable for variabletype='MEAS'.

So in my display i want to have another column ReadBackValue next to the variable name and display the value. But the columns should be displayed only for SET variable types and not for MEAS variable types.
i tried to change the query and i could reach only that, i get the readbackvalue column for each variable.

something like

loadpoints loadpointsteps step AD1Out(ADcard_1)(SET)ReadBackValue N (SET) ReadBackValue Q_quer(Emi2)(MEAS) T_I(Emi2)(MEAS).....

Can you please help me out with the query or to achieve in pl/sql as anyhow i use pl/sql to write the contents into excel sheet. so no limitation.

thanks.

Tom Kyte
June 20, 2004 - 10:20 am UTC

a column either

a) appears in a result set
b) does not appear in a result set

you will not, cannot have differing sets of columns row by row.


You can use decode and/or case easily to NULL out a value when some column is some value -- the column will be there -- but "empty" on that row.

A reader, January 03, 2007 - 7:31 am UTC

Hi Tom,

I have a requirement like this.

i have one table which has agreement information and another table which has service information. i have to join these two tables and for a particular product, i have to find out which all service are there as a single record. The problem is for a particular product, we can have many services involved and so the no. of columns has to be decided dynamically. even this is fine. The main probelm is the different column names should be different service_name.
Say for eg. for a product, i have 3 services namely A,B,C then i should have 3 columns and the names should be A,B and C.
There are 2 approaches as you have mentioned. One using Sql and Another using Pl/Sql.
If i have to use Pl/Sql, then i have to create a table(this table will be used for reporting) with these many(346) columns and if a particular service exist, then the values will be 'Yes'. But i dont think having 346 columns is a good idea.
So can you suggest any method where we can do the same using sql?

A reader, January 03, 2007 - 7:32 am UTC

For the above post, Oracle version is 9.2.0.8

A Reader, January 03, 2007 - 8:50 am UTC

Hi tom,
For the above problem, i got the solution. i used the same procedure which was given above with modifications.Now i am able to display all the column names. But still there is one problem. In that procedure, the cursor was passed as a parameter. Is there any way we can avoid this? Because this procedure will be used an a reporting tool and i m not sure we can pass cursor as parameters there. So any ideas?
Tom Kyte
January 05, 2007 - 8:10 am UTC

insufficient data to answer, what precisely would you LIKE to pass, be more precise with your needs here.

Dynamic column selection

Girish Kumar Sharma, November 26, 2007 - 4:55 am UTC

Hi,

SCOTT@orcl> ed
Wrote file afiedt.buf

1 create or replace procedure p_dynamic_sql
2 is
3 v_sql varchar2(4000);
4 begin
5 v_sql := 'select ';
6 for r in (select column_name
7 from user_tab_cols
8 where table_name = 'EMP'
9 and column_name not in ('EMPNO','JOB')) loop
10 v_sql := v_sql || r.column_name || ',';
11 end loop;
12 v_sql := rtrim(v_sql, ',') || ' from emp';
13 dbms_output.put_line( v_sql );
14 -- do something with SQL (open cursor, etc.)
15* end;
SCOTT@orcl> /

Procedure created.

SCOTT@orcl> set serveroutput on
SCOTT@orcl> exec p_dynamic_sql
select ENAME,MGR,HIREDATE,SAL,COMM,DEPTNO from emp

PL/SQL procedure successfully completed.

1. How can i get the data as query output of this proecedure ?
2. If i wish to tell the name of columns at execution time of the procedure i.e. dynamic; how it can be possible?
Thanks
Tom Kyte
November 26, 2007 - 1:04 pm UTC

what is your goal with this routine, what is its intended use. Where does it fit into your architecture.

I can tell you how to do this, I just want to know WHAT you are doing here and why.

Because it isn't a good idea in general.

Dynamic Column Selection

Girish Kumar Sharma, November 26, 2007 - 11:02 pm UTC

Hi,

Thanks for your reply.

>what is your goal with this routine.

I have a table in which date,veh1,veh2,veh3,veh4....veh100 are the columns; i.e. vehicle1, vehicle2 etc. Everyday, in these 100 vehicles; our driver fills the fuel and submit the petrol bill for their vehicle. Normally, all vehicle goes for trip everyday; except 1 or 2 (may be driver is on leave or vehicle is under repair). So in the application, we feed the petrol bill as we receive at random.

After feeding the petrol bill data, i have to get the select query of only those vehicles; in which today's amount is not null or zero or by other mean (store department) i comes to know that such and such vehicle number were not went on trip i.e. vehicle no. 52 and 73. So, i have to write everyday select query like select date, veh1,veh2...veh51,veh53...veh72,veh74...veh100 from veh.

So rather mentioning all the columns name, i wish to mention only those column names which vehicle number were not went on trip today; in the select query.

>Because it isn't a good idea in general.

Please guide me, how by other mean i can set the random columns in the select query.

Thanks & Regards.
Tom Kyte
November 27, 2007 - 3:28 pm UTC

ok, i give up.

why the heck would you do this - why why why would you use veh1..veh100.

man oh man.


we will FIX your data in a view temporarily until you do it yourself correctly - using ROWS, not columns.

the view will look like this:
create view v as
with data as (select level l from dual connect by level <= 10 )
select dt,
       'veh' || l veh_name,
       case when l = 1 then veh1
            when l = 2 then veh2
            ...
            when l = 100 then veh100
        end veh_value
  from data, t;


then, your problem is trivial. Consider:

ops$tkyte%ORA9IR2> declare
  2          l_sql long := 'create table t ( dt date';
  3  begin
  4          for i in 1 .. 100
  5          loop
  6                  l_sql := l_sql || ', veh' || i || ' int';
  7          end loop;
  8          execute immediate l_sql || ')';
  9
 10          l_sql := '
 11  create view v
 12  as
 13  with data as (select level l from dual connect by level <= 10 )
 14  select dt,
 15         ''veh'' || l veh_name,
 16             case ';
 17
 18          for i in 1 .. 100
 19          loop
 20                  l_sql := l_sql || ' when l = ' || i || ' then veh' || i;
 21          end loop;
 22          execute immediate l_sql || ' end veh_value from data, t';
 23  end;
 24  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.06
ops$tkyte%ORA9IR2>
ops$tkyte%ORA9IR2> insert into t ( dt, veh1, veh2, veh3  ) values ( trunc(sysdate), 0, 1, 2 );

1 row created.

Elapsed: 00:00:00.01
ops$tkyte%ORA9IR2>
ops$tkyte%ORA9IR2> select * from v where dt = trunc(sysdate) and nvl(veh_value,0) <> 0;

DT        VEH_NAME                                     VEH_VALUE
--------- ------------------------------------------- ----------
27-NOV-07 veh2                                                 1
27-NOV-07 veh3                                                 2

Elapsed: 00:00:00.02
ops$tkyte%ORA9IR2>


and yes, if you have an index on DT, it'll still work and be used:


ops$tkyte%ORA9IR2> create index t_idx on t(dt);

Index created.

Elapsed: 00:00:00.01
ops$tkyte%ORA9IR2> exec dbms_stats.set_table_stats( user, 'T', numrows=> 10000, numblks => 1000 );

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
ops$tkyte%ORA9IR2> @at
ops$tkyte%ORA9IR2> column PLAN_TABLE_OUTPUT format a72 truncate
ops$tkyte%ORA9IR2> set autotrace traceonly explain
ops$tkyte%ORA9IR2> select * from v where dt = trunc(sysdate) and nvl(veh_value,0) <> 0;
Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=5 Bytes=225)
   1    0   VIEW OF 'V' (Cost=4 Card=5 Bytes=225)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=5 Bytes=500)
   3    2       NESTED LOOPS (Cost=4 Card=5 Bytes=565)
   4    3         VIEW (Cost=2 Card=1 Bytes=13)
   5    4           CONNECT BY (WITHOUT FILTERING)
   6    5             TABLE ACCESS (FULL) OF 'DUAL' (Cost=2 Card=1)
   7    3         INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=1 Card=40)



ops$tkyte%ORA9IR2> @atoff
ops$tkyte%ORA9IR2> set autotrace off

to: Girish Kumar Sharma

A reader, November 27, 2007 - 2:55 am UTC

Hello,

I think you've got a fundamentally wrong schema/table design. Instead of trying to dynamically construct the query you should refactor your schema:
as I understand it, you've got table like
create table fuel_consuption (
[id number,]
day date,
veh1 number,
veh2 number,
....
veh100 number));

A better approach is to have a table like
create table fuel_consumption (
id number,
day date,
vehicle_id number,
vehicle_value number);

So one day's report gets saved into 100 rows of a 4 column table instead of into one row of a 102 column table.

To get only those vehicles of a particular day with existing data you use:
select day, vehicle_id, vehicle_value from
fuel_consumption where day = :some_day

Benefits of this approach over yours are (at minumum):
1. easiness (much shorter queries/table)
2. flexibility (you can add now a 101th vehicle just by adding an id (hopefully restricted to a child table)

I believe the process just described is called table/schema normalization. Google it :).

Your original request could be fullfied by using 'execute immediate' and probably 'open cursor for' try plsql documentation for these.


Dynamic Column Selection

Girish Kumar Sharma, November 27, 2007 - 4:25 am UTC

Hello,

>I think you've got a fundamentally wrong schema/table design.

Yes, you are right, but at this moment i do'nt want to change any structure; because it will require application re-designing and common solution to existing system. By the end of year, i wish to take changes.

Right now, if i would able to get the data with excluding those columns; in which amount is null or zero or a certain column names; then it will smooth my working and time saving too. Something like:
SCOTT@orcl> exec <someprocedure>
Enter Number of Vehicles not had trip:45,73

Date Veh1 Veh2 Veh3.. Veh44 Veh46.. Veh72 Veh74.. Veh100
....

Thanks & Regards
Tom Kyte
November 27, 2007 - 3:38 pm UTC

see above.

Dynamic Column Selection

Girish Kumar Sharma, November 28, 2007 - 4:32 am UTC

Hello Sir,

My Sincere thanks for your reply and efforts.
Finally i got the following function code which is something like this: (only one column will be excluded)

create or replace function dynamic_col_sel (pColumn varchar2,pTable varchar2)
return sys_refcursor as
c sys_refcursor;
v_sql varchar2(4000);
begin
v_sql := 'select ';
for r in (select column_name
from user_tab_cols
where table_name = pTable
and column_name not in (pColumn)) loop
v_sql := v_sql || r.column_name || ',';
end loop;
v_sql := rtrim(v_sql, ',') || ' From ' || pTable;
open c for v_sql;
return c;
end;

SYS@orcl>set serveroutput on
SYS@orcl>var c refcursor
SYS@orcl>execute :c := dynamic_col_sel('VEH73','T');
SYS@orcl>print c;

Now it is showing all the columns except VEH73; (here column name should be in UPPER case only).

Once again....
Thanks and warm Regards
Girish Kumar Sharma
Ajmer (India)

Tom Kyte
November 28, 2007 - 10:50 pm UTC

i give up.

totally.

i give up.

as I say to my kids "osh kosh by gosh"...


just giving up...................................



impact of questions to Tom's health

A reader, November 29, 2007 - 6:36 am UTC

Hello Tom,

thank you for this site, I have learned a lot.

q:
<quote>
i give up.

totally.

i give up.

as I say to my kids "osh kosh by gosh"...


just giving up...................................
</quote>

Do you swear or laugh before answering like this? I've laughed several times during reading of this thread.

Tom Kyte
November 29, 2007 - 8:54 am UTC

I don't always laugh.

Sorry

A reader, November 29, 2007 - 12:04 pm UTC

We apologize on behalf of Mr.Girish.


genrate column name based on records

hardik bhalani, December 02, 2010 - 8:08 am UTC

Here is the data...

Row# CUST_ID ELE_NAME COUNT
1 126 ACC 5
2 126 COM 15
3 126 COJ 10
4 126 COS 35

Now the requierment is to create query where the ELE_NAME values will be populated as Column Name and ELE_VALUE will be value for dynamic column names... We cant Hard Code ELE_NAME bcoz values might change...

The View should populate the data in the following way

Table Column Names : ACC CON COJ COS
Table Values: 5 15 10 35

Tom Kyte
December 07, 2010 - 9:10 am UTC

that cannot happen unless the set of ELE_NAME's is known at view creation table.

You can create a stored procedure that returns a REF CURSOR with the right column names - but it'll take two steps:

step 1: run a query to get the names
step 2: open a dynamically constructed query that uses those names

I'd be willing to demonstrate for you - but unfortunately for some reason I don't seem to have your table installed in my database and you forgot to give me a create table/inserts!

dynamically generate column

hardik bhalani, December 02, 2010 - 11:16 pm UTC

Here is the data...

Row# CUST_ID ELE_NAME COUNT
1 126 ACC 5
2 126 COM 15
3 126 COJ 10
4 126 COS 35

Now the requierment is to create query where the ELE_NAME values will be populated as Column Name and ELE_VALUE will be value for dynamic column names... We cant Hard Code ELE_NAME bcoz values might change...

The View should populate the data in the following way

Table Column Names : ACC CON COJ COS
Table Values: 5 15 10 35


Generate column name dynamically plus rollup

Karen T, June 30, 2011 - 12:23 am UTC

Forgive me, but I have been searching many threads here and still do not understand how to generate column names dynamically using pivot clause. I'm on 11gR1.
CREATE TABLE "PIVOT_EXAMPLE"
  (
    "EMP"   VARCHAR2(3),
    "PAY_CODE_ID" VARCHAR2(3),
    "HOURS"       NUMBER
  );

Insert into PIVOT_EXAMPLE (EMP,PAY_CODE_ID,HOURS) values ('ANN','PC4',8.5);
Insert into PIVOT_EXAMPLE (EMP,PAY_CODE_ID,HOURS) values ('ANN','PC2',8.5);
Insert into PIVOT_EXAMPLE (EMP,PAY_CODE_ID,HOURS) values ('BOB','PC5',8.5);
Insert into PIVOT_EXAMPLE (EMP,PAY_CODE_ID,HOURS) values ('BOB','PC0',8.5);
Insert into PIVOT_EXAMPLE (EMP,PAY_CODE_ID,HOURS) values ('JIM','PC5',8.5);
commit;

I understand the pivot:
with pivot_data as 
(select * from pivot_example)
 select * from pivot_data
    PIVOT (
              SUM(hours) 
       FOR PAY_CODE_ID
IN ('PC4','PC2','PC5','PC0'));

Which yields:
EMP  'PC4'  'PC2'  'PC5'  'PC0'
ANN  8.5    8.5
BOB                8.5    8.5
JIM                8.5 



But I would like to know how to compute the column names based on whatever values are in the table. I would also like to know how to combine pivot with rollup so I can have a totals row, for example:
EMP  'PC4'  'PC2'  'PC5'  'PC0'
ANN  8.5    8.5
BOB                8.5    8.5
JIM                8.5 

TOT  8.5    8.5    17     8.5

Thank you very much in advance.
Tom Kyte
July 01, 2011 - 8:15 am UTC

You cannot. You would have to

a) read the table to determine the NAME and NUMBER (cardinality) of the columns in question

b) dynamically build a query that incorporates them

SQL needs to know at parse time

a) how many columns there will be
b) what their names are

Dynamic Pivot

Lal, September 10, 2012 - 7:54 am UTC

Tom,
We can use the xml option to avoid making the query dynamic right?
But the problem is the result set is an xml.
Is there any way to convert that xml into a tabular format or result set something like

select * /*decode xml as tabular format??*/ from(
SELECT *
FROM   (SELECT product_code, quantity
        FROM   pivot_test)
PIVOT XML (SUM(quantity) AS sum_quantity FOR (product_code) IN (SELECT DISTINCT product_code 
                     FROM pivot_test 
                    WHERE  id < 10))
);

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library