Home>Question Details



Puneet -- Thanks for the question regarding "Pivot Query", version 8.1.5

Submitted on 14-Sep-2000 9:22 Central time zone
Last updated 14-Jul-2009 15:08

You Asked

Please state an example of pivot query.
I know that in cardinality I've 2 columns, so how can I show them in columnar form and I 
know the cardinality is 4. 
Please also illustrate method of Varray and Nested Tables.

Regards
Puneet 

and we said...

Here is an example of a pivot query.  Say you have the following set of data:

scott@DEV816> select job, deptno, count(*)
  2    from emp
  3   group by job, deptno
  4  /

JOB           DEPTNO   COUNT(*)
--------- ---------- ----------
ANALYST           20          2
CLERK             10          1
CLERK             20          2
CLERK             30          1
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
PRESIDENT         10          1
SALESMAN          30          4

9 rows selected.


And you would like to make DEPTNO be a column.  We have 4 deptno's in EMP, 10,20,30,40.  
We can make columns dept_10, dept_20, dept_30, dept_40 that have the values that are 
currently in the count column.  It would look like this:


scott@DEV816> 
scott@DEV816> select job,
  2         max( decode( deptno, 10, cnt, null ) ) dept_10,
  3         max( decode( deptno, 20, cnt, null ) ) dept_20,
  4         max( decode( deptno, 30, cnt, null ) ) dept_30,
  5         max( decode( deptno, 40, cnt, null ) ) dept_40
  6    from ( select job, deptno, count(*) cnt
  7             from emp
  8            group by job, deptno )
  9   group by job
 10  /

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4


That has pivoted the CNT column by deptno across job.

That works if you know the domain of deptno's.  What if you didn't though.  What if you 
wanted JOB to be the column instead and leave deptno in the rows?  You might not know of 
all of the possible jobs, or there might be 100's of them.  We can use object types to 
pivot then:


scott@DEV816> create or replace type myScalarType as object
  2  ( job  varchar2(30),
  3    cnt  number
  4  )
  5  /

Type created.

scott@DEV816> create or replace type myArrayType as table of myScalarType
  2  /

Type created.

scott@DEV816> 
scott@DEV816> column x format a40 word_wrapped
scott@DEV816> select deptno,
  2         cast ( multiset( select job, count(*) cnt
  3                            from emp
  4                           where emp.deptno = dept.deptno
  5                           group by job ) as myArrayType ) x
  6    from dept
  7  /

    DEPTNO X(JOB, CNT)
---------- ----------------------------------------
        10 MYARRAYTYPE(MYSCALARTYPE('CLERK', 1),
           MYSCALARTYPE('MANAGER', 1),
           MYSCALARTYPE('PRESIDENT', 1))

        20 MYARRAYTYPE(MYSCALARTYPE('ANALYST', 2),
           MYSCALARTYPE('CLERK', 2),
           MYSCALARTYPE('MANAGER', 1))

        30 MYARRAYTYPE(MYSCALARTYPE('CLERK', 1),
           MYSCALARTYPE('MANAGER', 1),
           MYSCALARTYPE('SALESMAN', 4))

        40 MYARRAYTYPE() 

Reviews    
4 stars how about the other way round   May 13, 2002 - 11am Central time zone
Reviewer: A reader 
Hi Tom

Can we use decode to convert a column into a row?. For example

ename   dname
------  -------
FORD    FINANCE

into

details
--------
FORD
FINANCE

this is probably a strange requirement but it has been asked :( 


Followup   May 13, 2002 - 1pm Central time zone:

yes.

  1  select decode( r, 1, to_char(deptno), 2, dname, 3, loc )
  2  from dept, (select rownum r from all_objects where rownum <= 3 )
  3* where deptno = 10
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

DECODE(R,1,TO_CHAR(DEPTNO),2,DNAME,3,LOC
----------------------------------------
10
ACCOUNTING
NEW YORK
 

4 stars Similar Problem   May 13, 2002 - 2pm Central time zone
Reviewer: Mike Wilson from Los Angeles, CA
I have a simlar problem to the above of pivoting tables where I don't know the domain of the pivot 
columns (or they fluctuate).  Because of this I wrote a small domain lookup function that uses 
auto-binding and looks up the column values.  I create a lookup table that lists the field names to 
be retreived and use a cartesian join to do my lookups and then exclude all of the null values.  It 
forces auto-binding also.

CREATE OR REPLACE  FUNCTION DLOOKUP (vField in varchar2, 
    vTable in varchar2, vWhere in varchar2 default NULL) return 
    varchar2
    AUTHID CURRENT_USER
    as
       vVar varchar2(4000);
       vQuery varchar2(4000);
    begin
       execute immediate 'alter session set cursor_sharing=force';
       vQuery := 'select '||vField||' from '||vTable;
       if (vWhere is not NULL) then
            vQuery := vQuery ||' where '||vWhere;
       end if;
       execute immediate vQuery into vVar;
       return vVar;
       exception when others then
         return null;
   end;
;

This works pretty well, although the cartesian join is very expensive.  With the example above 
though using an array, how would you get it to actually look like a table?  Is there a view you can 
wrap around the multiset output to make it look like an actual table?

 


Followup   May 13, 2002 - 2pm Central time zone:

show me how this pivots?  I don't get it -- seems to just do a select into of a single row/column

I don't see any cartesian products here at all?


If you have my book -- the last section of the analytic functions chapter shows how to pivot any 
query using a ref cursor and some simple query rewrite techniques. 

5 stars theory behind pivot query   May 13, 2002 - 6pm Central time zone
Reviewer: A reader 
Hi Tom

I noticed you said carteasian product, the trick or theory behind pivoting a result set is by 
having a carteasian product the decode the columns, am I correct?

so basically from your example

select decode( r, 1, to_char(deptno), 2, dname, 3, loc )
from dept, (select rownum r from all_objects where rownum <= 3 )
where deptno = 10

we first get

1, 10,  accounting, new york
2, 10,  accounting, new york
3, 10,  accounting, new york

then we decode 1 to deptno, 2 to accounting, 3 to new york to get

10
ACCOUNTING
NEW YORK


is my understanding correct?

cheers
 


Followup   May 13, 2002 - 7pm Central time zone:

Exactly, dead on. 

4 stars Un-pivot example for comment ...   May 13, 2002 - 10pm Central time zone
Reviewer: Mike Wilson from Los Angeles, CA
Sorry, for the previous incomplete post.  Hit enter while incomplete in thought.  You are correct, 
un-pivoting a table in the above case doesn't require a cartesian join.  

To be brief I frequently am asked to produce un-pivots of data *and* calculate values.  These 
calculations may also vary by department.  Hard coding these views for a rather fluid calculation 
system and then to keep versions per department wouldn't be feasible as I would have to have a 
different version of the view per department and our department structures and calculation models 
change monthly.  

I have read your examples in your excellent book on Analytic Functions (way cool) but they deal 
with pivoting a table (rows to columns) not columns to rows.  Because of this I have resorted to 
writting my own domain lookup function that uses bind variables and a lookup table that allows my 
calculation model to vary by department.

As I so ineptly responded before, rather than hard-coding a view I wrote a small domain lookup 
function that allows me flexibility (at the expense of compute time).  I simply wanted to share the 
function with the readers and see if they had any comments and to see if you might show me how to 
get rid of the dlookup function by hardcoding the below example into a simple view that can 
un-pivot the data *and* calculate values per row per deptment.  At minimum I thought my small 
function might be useful for other readers.  See below for an example of un-pivoting a table that 
seems more maintainable to me than using static views if your view needs to change frequently.


Pretend that I have given two departments id's 1 and 2 and that the values of a and b are important 
to a calculation they need.  Please note that at any time a department may appear or disappear and 
that each department calculates their values differently.

temp table
id    a    b    
=======================================
1    1    2        
2    4    5        


Per department, per column this is the calculation that each one wants.

temp_lkup table
id    col    calc
=======================================
1    a    a
1    b    b*2
2    a    a+6
2    b    b*3

desired output
id    col    orig    calc    val
=======================================
1    a    1    a    1
1    b    2    b*2    4    
2    a    4    a+6    10
2    b    5    b*3    15


<snip>
create table temp (id integer, a integer, b integer)
/
insert into temp select 1,1,2 from dual
/
insert into temp select 2,4,5 from dual
/
commit
/

select * from temp
/

create table temp_lkup (id integer, col varchar2(16), calc varchar2(16))
/
insert into temp_lkup select 1, 'a', 'a' from dual
/
insert into temp_lkup select 1, 'b', 'b*2' from dual
/
insert into temp_lkup select 2, 'a', 'a+6' from dual
/
insert into temp_lkup select 2, 'b', 'b*3' from dual
/
commit
/
select * from temp_lkup
/

CREATE OR REPLACE  FUNCTION DLOOKUP_BIND_ONE  (vField in 
    varchar2, vTable in varchar2, vWhere in varchar2 default NULL,
    vBind in varchar2) return varchar2
AUTHID CURRENT_USER
    as
       vVar varchar2(4000);
       vQuery varchar2(4000);
    begin

       -- exit if field is null
       if vfield is null then
         return null;
       end if;

       -- exit if table is null
       if vtable is null then
         return null;
       end if;

       vQuery := 'select '||vField||' from '||vTable;
       if (vWhere is not NULL) then
            vQuery := vQuery ||' where '||vWhere;
       end if;
       execute immediate vQuery into vVar using vBind;
       return vVar;
       exception
         when NO_DATA_FOUND then
           return null;
         when others then
           raise;
   end;
/

-- now to get the result set
select temp.id, temp_lkup.col, temp_lkup.calc,
  to_number(nvl(dlookup_bind_one(col, 'temp', 'id=:a', temp.id),0)) orig,
  to_number(nvl(dlookup_bind_one(calc, 'temp', 'id=:a', temp.id),0)) val
  from temp, temp_lkup
  where temp.id = temp_lkup.id
/
</snip>
        ID COL              CALC                   ORIG        VAL
---------- ---------------- ---------------- ---------- ----------
         1 a                a                         1          1
         1 b                b*2                       2          4
         2 a                a+6                       4         10
         2 b                b*3                       5         15


 


3 stars   May 16, 2002 - 7am Central time zone
Reviewer: Sagi from India
Once again it was great tom.

I executed the below Query:

select decode( r, 1, to_char(deptno), 2, dname, 3, loc )  || chr(10) "Col2Row " 
from dept, (select rownum r from all_objects where rownum <= 3)
order by deptno 

Output:
=======
10
ACCOUNTING
NEW YORK
20
RESEARCH
DALLAS
30
SALES
CHICAGO
40
OPERATIONS
BOSTON

Question: How do we get say 2 empty blank lines after each deptno. I tried using CHR(10) or 
CHR(13). But could not get the output.

Thanx in advance.

REgards. 


Followup   May 16, 2002 - 1pm Central time zone:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select decode( r, 1, to_char(deptno)  || chr(10), 2, dname, 
3, loc )  || chr(10) "Col2Row "
  2  from dept, (select rownum r from all_objects where rownum <= 3)
  3  order by deptno
  4  
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> /

Col2Row
------------------------------------------
10


ACCOUNTING
NEW YORK
20


RESEARCH
DALLAS
30


SALES
CHICAGO
40


OPERATIONS
BOSTON

12 rows selected. 

4 stars general purpose pivot function   May 16, 2002 - 1pm Central time zone
Reviewer: Mikito Harakiri 
Mike, could you please demonstrate what you achieve on emp table -- I don't get it.

It might be possible to write a general purpose pivot table function. The problem is the return 
type, which we can't declare of being any specific type. Maybe anydata/anydtaset might help... 


Followup   May 17, 2002 - 7am Central time zone:

I did write a general purpose pivot function -- its in the analytic function chapter of my book.  
Uses a simple query rewrite to pivot and returns a dynamically opened ref cursor (works in 8i and 
up). 

3 stars got your code, thank you   May 17, 2002 - 9pm Central time zone
Reviewer: Mikito Harakiri 
begin
  my_pkg.pivot
  (p_max_cols => 4,
   p_query => 'select job, count(*) cnt, deptno,
                  row_number() over (partition by job order by deptno) rn
                  from emp 
                  group by job, deptno',
   p_anchor => my_pkg.array('JOB'),
   p_pivot  => my_pkg.array('DEPTNO', 'CNT'),
   p_cursor => :x );
end;

This is not quite what I meant. What if pivoting is just a subquery of a larger query? Is there a 
way to call your function from a sql query?
 


Followup   May 18, 2002 - 10am Central time zone:

I cannot see how pivoting is could be a "subquery" of a larger query.  That just doesn't compute 
with me. 

5 stars good point   May 20, 2002 - 4pm Central time zone
Reviewer: Mikito Harakiri 


5 stars Can we have the above output using Multiset and ArrayType   July 29, 2002 - 2am Central time zone
Reviewer: Yamani from KSA
Hi Tom,

Can we have the same out put generated on the above example:

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

But using the multiset and ArrayType.  
If yes can you pleas show us how?

Thanks,

 


Followup   July 29, 2002 - 7am Central time zone:

they won't be columns, you would have 2 columns -- the JOB and then a single array that is the list 
of counts by deptno.  

It would be "ugly" but it can be done.  The array would have to be an array of object types that 
had the deptno and cnt in them (an array of records in effect).



ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myScalarType as object
  2  ( deptno number,
  3    cnt       number
  4  )
  5  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type myArrayType as table of MyScalarType
  2  /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> column data format a40
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select job,
  2     cast( multiset( select myScalarType( deptno, count(*) )
  3                       from emp e2
  4                      where e2.job = emp.job
  5                      group by deptno ) as myArrayType ) data
  6    from (select distinct job from emp ) emp
  7  /

JOB       DATA(DEPTNO, CNT)
--------- ----------------------------------------
ANALYST   MYARRAYTYPE(MYSCALARTYPE(20, 2))
CLERK     MYARRAYTYPE(MYSCALARTYPE(10, 1), MYSCALA
          RTYPE(20, 2), MYSCALARTYPE(30, 1))

MANAGER   MYARRAYTYPE(MYSCALARTYPE(10, 1), MYSCALA
          RTYPE(20, 1), MYSCALARTYPE(30, 1))

PRESIDENT MYARRAYTYPE(MYSCALARTYPE(10, 1))
SALESMAN  MYARRAYTYPE(MYSCALARTYPE(30, 4))

ops$tkyte@ORA817DEV.US.ORACLE.COM> 

 

5 stars Got it !!   August 26, 2002 - 11pm Central time zone
Reviewer: A reader 
Pivot queries, had absolute no idea on how to write one.
Add a dash of AskTom and I have cooked up some awesome
queries that are keeping users happy and Managers off my back.

You are absolute legend Tom... 


4 stars   January 16, 2003 - 2pm Central time zone
Reviewer: A reader 
Hi Tom, please help to find out what is wrong in my query:
select dept,
max(decode(to_char(dateused,’DY’), ‘MON', cnt, null)) "mon",
max(decode(to_char(dateused,’DY’), ‘TUE', cnt, null)) "tue",
max(decode(to_char(dateused,’DY’), ‘WED', cnt, null)) "wed",
max(decode(to_char(dateused,’DY’), ‘THU', cnt, null)) "thu",
max(decode(to_char(dateused,’DY’), ‘FRI', cnt, null)) "fri",
max(decode(to_char(dateused,’DY’), ‘SAT', cnt, null)) "sat",
max(decode(to_char(dateused,’DY’), ‘SUN', cnt, null)) "sun"
from (select dept,
               decode(to_char(dateused,’DY’),
                 'MON’, 'mon',    
                 'TUE', 'tue',
                 'WED', 'wed',
                 'THU', 'thu',
                 'FRI', 'fri',
                 'SAT', 'sat',
                 'SUN’, 'sun') t,
               count(*) cnt
          from visits v, users u
where v.ss = u.ss
and to_date(v.dateused, 'DD-MON-RR') > = to_date('1-AUG-02', 'DD-MON-RR')   
and to_date(v.dateused, 'DD-MON-RR')  <  to_date('1-SEP-02', 'DD-MON-RR')   
group by dept,
decode(to_char(dateused,’DY’),
                   ‘MON’,’mon’,    
                 'TUE', 'tue',
                 'WED', 'wed',
                 'THU', 'thu',
                 'FRI', 'fri',
                 'SAT', 'sat',
                 'SUN’, 'sun'      ))
  group by dept;
ERROR at line 8:
ORA-00904: invalid column name
SQL> desc visits
 ID         NOT NULL VARCHAR2(7)
 LOCATION   NOT NULL NUMBER(15,2)
 DATEUSED   NOT NULL DATE   
SQL> desc users
 ID         NOT NULL VARCHAR2(7)
 DEPT       NOT NULL VARCHAR@(80)
 
Thanks 
L. 


Followup   January 16, 2003 - 7pm Central time zone:

well, you got that v.ss = u.ss in there as well -- so this is a hacked example at best.... but...

line 8 has 

max(decode(to_char(dateused,'DY'), 'MON', cnt, null)) "mon",


but by the time you get there, dateused doesn't exist.  

dept, t, cnt 

they exist, but dateused -- long gone. 

3 stars Can this pivoting apply here too?   June 20, 2003 - 1pm Central time zone
Reviewer: Godwin from Ghana
Hi Tom, I have this records in table A as
stno   course code
----   -----------
1000   SOCI
1000   ECONS
1000   MATHS
1000   ANAT
1002   SOCI
1002   ECONS
1002   MATHS
1002   ANAT

and I want the above record to be inserted into a 
table B(stno,subj1,subj2,subj3,subj4) in a row format such as :
stno  subj1  subj2  stubj3  stubj4
----  ----   -----  ------  -------
1000  SOCI   ECONS   MATHS  ANAT

How can the pivoting be performed on this?
If it is impossible, is there any way out?
Thanks for your usual co-operation.  
 


Followup   June 20, 2003 - 5pm Central time zone:

if there is a fixed maximum number of course codes per stno, yes


select stno, 
       max(decode(rn,1,course_code)) s1,
       ...
       max(decode(rn,N,course_code)) sN
  from ( select stno, course_code, 
                row_number() over (partition by stno order by course_code ) 
           from t )
 group by stno
/

 

3 stars Great-the answer is right inside your book-expert-one-on-one   June 23, 2003 - 6am Central time zone
Reviewer: Godwin from Ghana
Hi posted the question to you before I realized the answer was in your book. I just got your book a 
few days ago and am now studying it.
But permit me to ask for a clarification on the answer you just posted. What if the maximum course 
code per stno is not known? that is if there are more than thousands course codes? In this case, I 
believe the selected records can't be inserted into a table since the column in the inserting table 
may not be known. But can the query to display the records in this case be possible? can you show 
with example?
Thanks. 


Followup   June 23, 2003 - 8am Central time zone:

see the original answer -- you can use a COLLECTION, or you could even use a cursor variable.


select stno, CURSOR( select course_code from t t2 where t2.stno = t.stno )
  from (select distinct stno from t ) T
/


like this;



scott@ORA920> select deptno, Cursor(select ename from emp e2 where e2.deptno = emp.deptno)
  2  from (select distinct deptno from emp ) emp;

    DEPTNO CURSOR(SELECTENAMEFR
---------- --------------------
        10 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
CLARK
KING
MILLER

        20 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
SMITH
JONES
SCOTT
ADAMS
FORD

        30 CURSOR STATEMENT : 2

CURSOR STATEMENT : 2

ENAME
----------
ALLEN
WARD
MARTIN
BLAKE
TURNER
JAMES

 

5 stars Thanks Tom   July 15, 2003 - 6pm Central time zone
Reviewer: Sirisha Swahari from ColoradoSprings, CO USA
This example simplified my work a lot.
Keep up the good work Tom. 


4 stars   July 25, 2003 - 3am Central time zone
Reviewer: ARC from Singapore
Very good explanation on pivot query.
I have info in table like this.
Table: TT
s date,
a number,
b number,
c nuber
SQL> select * from tt;
S                 A         B         C
--------- --------- --------- ---------
25-JUL-01       100      1000      2000
20-JUL-01       200      3000      4000

I would like to display info as below
25-JUL-01
1  100
2  1000
3  2000

20-JUL-01
1  200
2  3000
3  4000

No of columns are not fixed.

Please help me in regard. Thanking you in advance.




  
 


Followup   July 25, 2003 - 7am Central time zone:

the number of columns MUST be known, else you cannot write the query and to write the query is 
easy:


select dt, decode(r,1,a,2,b,3,c)
  from t, (select rownum r from all_objects  where rownum <= 3 )
/ 

5 stars pivot   August 1, 2003 - 3am Central time zone
Reviewer: ARC from Singapore
Hi Tom,
Need your help again.
I have query like below.

break on report
compute sum of deptno_10 on report 
compute sum of deptno_20 on report 
compute sum of deptno_30 on report 
compute sum of total on report

select     job,
    max(decode(deptno, 10, cnt, 0)) DEPTNO_10,
    max(decode(deptno, 20, cnt, 0)) DEPTNO_20,
    max(decode(deptno, 30, cnt, 0)) DEPTNO_30,
    max(decode(deptno, 10, cnt, 0)) +
    max(decode(deptno, 20, cnt, 0)) +
    max(decode(deptno, 30, cnt, 0)) TOTAL
from    (select deptno deptno, job job, count(*) cnt from emp
    group by deptno, job)
group by job;

JOB       DEPTNO_10 DEPTNO_20 DEPTNO_30     TOTAL
--------- --------- --------- --------- ---------
ANALYST           0         2         0         2
CLERK             1         2         1         4
MANAGER           1         1         1         3
PRESIDENT         1         0         0         1
SALESMAN          0         0         5         5
          --------- --------- --------- ---------
sum               3         5         7        15

1)Is it possible to display 'TOTAL' insted of 'sum' in above report?

2)If deptno column values are changing how to handle above query in more generic?

Thanks you very much.


 


Followup   August 1, 2003 - 7am Central time zone:

1) don't think so, plus is what it is -- very very rudimentary, simple reporting tool.

2) if you have my book "Expert one on one Oracle" -- see the chapter on analytic functions.  I have 
a stored procedure there.  In short, you need to run a query to find the domain of deptnos so you 
can then write a dynamic sql query to pivot based on that set of values. 

4 stars for ARC: use LABEL to change sum to TOTAL   August 4, 2003 - 7pm Central time zone
Reviewer: Barbara Boehmer from Norwalk, CA USA
In response to ARC's question # 1:

Yes, you can use LABEL to change sum to TOTAL, as demonstrated below:


SQL> break on report
SQL> compute sum LABEL 'TOTAL' of deptno_10 on report
SQL> compute sum of deptno_20 on report
SQL> compute sum of deptno_30 on report
SQL> compute sum of total on report
SQL> select      job,
  2           max (decode (deptno, 10, cnt, 0)) deptno_10,
  3           max (decode (deptno, 20, cnt, 0)) deptno_20,
  4           max (decode (deptno, 30, cnt, 0)) deptno_30,
  5           max (decode (deptno, 10, cnt, 0)) +
  6           max (decode (deptno, 20, cnt, 0)) +
  7           max (decode (deptno, 30, cnt, 0)) total
  8  from        (select deptno, job, count (*) cnt
  9            from   emp
 10            group by deptno, job)
 11  group by job
 12  /

JOB        DEPTNO_10  DEPTNO_20  DEPTNO_30      TOTAL
--------- ---------- ---------- ---------- ----------
ANALYST            0          2          0          2
CLERK              1          2          1          4
MANAGER            1          1          1          3
PRESIDENT          1          0          0          1
SALESMAN           0          0          4          4
          ---------- ---------- ---------- ----------
TOTAL              3          5          6         14

SQL> 
 


Followup   August 4, 2003 - 7pm Central time zone:

awesome, thanks much! 

4 stars for ARC: another generic method when deptno column values are changing   August 4, 2003 - 7pm Central time zone
Reviewer: Barbara Boehmer from Norwalk, CA USA
In response to ARC's question # 2:

The following SQL to create SQL can be used to dynamically generate the query.  If you run the 
script below, it will create another script (query.sql) with the desired query, allowing for 
whatever values of deptno exist, then execute that script.  Since you seem to want to do this in 
SQL*Plus, you may like this method.

store set saved_settings replace
set echo off feedback off heading off pages 0 verify off
spool query.sql
prompt break on report
prompt compute sum label 'TOTAL' of deptno_10 on report
prompt compute sum of deptno_20 on report
prompt compute sum of deptno_30 on report
prompt compute sum of total on report
prompt select   job
select ', max (decode (deptno, ' || deptno || ', cnt, 0)) deptno_' || deptno
from   (select distinct deptno from emp)
/
select ', max (decode (deptno, ' || min_deptno || ', cnt, 0))'
from   (select min (deptno) min_deptno from emp)
/
select '+ max (decode (deptno, ' || other_deptnos || ', cnt, 0))'
from   (select distinct deptno other_deptnos from emp where deptno >
        (select min(deptno) from emp))
/
prompt as total
prompt from     (select   deptno, job, count (*) cnt 
prompt           from     emp
prompt           group by deptno, job)
prompt group by job
prompt /
spool off
start saved_settings
start query.sql
 


4 stars Pivot   August 4, 2003 - 9pm Central time zone
Reviewer: ARC from Singapore
Thank you very much Mr.Barbara Boehmer.   


5 stars Another Pivot   August 15, 2003 - 12pm Central time zone
Reviewer: A reader 
I would like to turn below query into Pivot. But anything I do gives me an error [ ORA-00904: 
invalid column name ] pointing somewhere in main FROM clause. 
Could you help?
I was trying to use example in your book but did not get far. I can't figure out what is going 
wrong. Error as you can see is not very descriptive and misleading.
Thank you in advance.

This:
select status, 
       to_char( trunc( status_date,'MM' ), 'Month') Month,
       count(1)
  from loanapplication a
 where a.status_date > to_date('12/31/02','MM/DD/YY') 
   and exists (select 1
                 from property b
                where  a.rec = b.loan_rec
                  and  b.addr_rec IN (select rec
                                        from address
                                       where zip >= '94101' and zip <= '94199')
               )    
group by status, to_char(trunc(status_date,'MM'),'Month')
order by Month

Into:
  select a.status,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), January, cnt, null) January,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), February, cnt, null) February,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), March, cnt, null) March,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), April, cnt, null) April,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), May, cnt, null) May,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), June, cnt, null) June,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), July, cnt, null) July,
         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt, null) August
    from (select status,
                 status_date,
                 count(*) cnt
            from loanapplication
           group by status, status_date
          ) a
   where a.status_date > to_date('12/31/02','MM/DD/YY')
     and exists (select 1
                   from property b
                  where a.rec = b.loan_rec
                    and b.addr_rec IN (select rec
                                         from address
                                        where zip >= '94101' and zip <= '94199')
                 )
   group by a.status, a.status_date
 


Followup   August 15, 2003 - 1pm Central time zone:

         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt, null) August
                                                               *
ERROR at line 9:
ORA-00904: "AUGUST": invalid identifier


i get that myself in 9i and:

         decode (to_char (trunc (a.status_date,'MM'),'Month'), August, cnt, null) August
                                                               *
ERROR at line 9:
ORA-00904: invalid column name


in 8i and before.


the * pointed me right to it.  Perhaps you need quotes about the 'August', and other months?

might be easier to just 

decode( to_char(a.status_date, 'mm' ), 1, cnt, null ) Jan,


? 

5 stars Same Pivot problem   August 15, 2003 - 2pm Central time zone
Reviewer: A reader 
Thank you for your recomendation. But I am still getting the error. It points to a column that is 
valid.
I am running 8.1.7.4 on Solaris 2.8.
 
 1   select a.status,
  2          decode( to_char(a.status_date, 'mm' ), 01, cnt, null ) "Jan",
  3          decode( to_char(a.status_date, 'mm' ), 02, cnt, null ) "Feb",
  4          decode( to_char(a.status_date, 'mm' ), 03, cnt, null ) "Mar",
  5          decode( to_char(a.status_date, 'mm' ), 04, cnt, null ) "Apr",
  6          decode( to_char(a.status_date, 'mm' ), 05, cnt, null ) "May",
  7          decode( to_char(a.status_date, 'mm' ), 06, cnt, null ) "Jun",
  8          decode( to_char(a.status_date, 'mm' ), 07, cnt, null ) "Jul",
  9          decode( to_char(a.status_date, 'mm' ), 08, cnt, null ) "Aug"
 10     from (select status,
 11                  status_date,
 12                  count(*) cnt
 13             from mm_loanapplication
 14            group by status, status_date
 15          ) a
 16      where a.status_date > to_date('12/31/02','MM/DD/YY')
 17        and exists (select 1 from mm_property b
 18  where  a.rec = b.loan_rec
 19  and  b.addr_rec IN (select rec
 20  from am_address
 21  where zip >= '94101' and zip <= '94199')
 22  )
 23* group by a.status, a.status_date
ENVPRD@PROD> /
group by a.status, a.status_date
                               *
ERROR at line 23:
ORA-00904: invalid column name 


Followup   August 15, 2003 - 3pm Central time zone:

it won't even get that far on my machine --  since a.rec does not exist -- you grouped it out.

ops$tkyte@ORA920> select a.status,
  2          decode( to_char(a.status_date, 'mm' ), 01, cnt, null ) "Jan",
  3          decode( to_char(a.status_date, 'mm' ), 02, cnt, null ) "Feb",
  4          decode( to_char(a.status_date, 'mm' ), 03, cnt, null ) "Mar",
  5          decode( to_char(a.status_date, 'mm' ), 04, cnt, null ) "Apr",
  6          decode( to_char(a.status_date, 'mm' ), 05, cnt, null ) "May",
  7          decode( to_char(a.status_date, 'mm' ), 06, cnt, null ) "Jun",
  8          decode( to_char(a.status_date, 'mm' ), 07, cnt, null ) "Jul",
  9          decode( to_char(a.status_date, 'mm' ), 08, cnt, null ) "Aug"
 10     from (select status,
 11                  status_date,
 12                  count(*) cnt
 13             from loanapplication
 14            group by status, status_date
 15          ) a
 16    where a.status_date > to_date('12/31/02','MM/DD/YY')
 17      and exists (select 1
 18                    from property b
 19                   where a.rec = b.loan_rec
 20                     and  b.addr_rec IN (select rec
 21                                           from address
 22                                          where zip >= '94101'
 23                                            and zip <= '94199')
 24                 )
 25  group by a.status, a.status_date
 26  /
                 where a.rec = b.loan_rec
                       *
ERROR at line 19:
ORA-00904: "A"."REC": invalid identifier



so, it is just not possible what you have there....  look deeper. 

5 stars   August 16, 2003 - 7pm Central time zone
Reviewer: A reader 


5 stars pivot two columns   September 10, 2003 - 6pm Central time zone
Reviewer: mo 
Tom:

I am trying to pivot two columns and can not get it to work. Do you have any hints. The quantity 
for each storage is not coming out right.  It is defaulting everything to the first value which is 
zero.


select stock_number, storage_code,qty_available,
                row_number() over(partition by stock_number order by stock_number nulls last) seq
               from (select a.stock_number, b.storage_code,
                    compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                    union all
                    select a.stock_number, b.storage_code,
                    compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, storage_receipt b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                   )
                  group by stock_number,storage_Code,qty_available

STOCK_NUMB STORAGE_CO
---------- ----------
QTY_AVAILABLE        SEQ
------------- ----------
AC006      ABC1
            0          1

AC006      ABC2
           50          2

AC006      ABC3
           50          3

AC006      ABC4
            0          4

AC006      ABC5
            0          5

AC006      ABC6
          250          6

AC006
            0          7


7 rows selected.


Then
select stock_number,
                  max(decode(seq,1,storage_code,null)) Loc#1,
                  max(decode(seq,1,qty_Available,null)) qty#1,
                  max(decode(seq,2,storage_code,null)) Loc#2,
                  max(decode(seq,1,qty_Available,null)) qty#2,
                  max(decode(seq,3,storage_code,null)) loc#3,
                  max(decode(seq,1,qty_Available,null)) qty#3,
                  max(decode(seq,4,storage_code,null)) loc#4,
                  max(decode(seq,1,qty_Available,null)) qty#4
                 from (
                select stock_number, storage_code,qty_available,
                row_number() over(partition by stock_number order by stock_number nulls last) seq
               from (select a.stock_number, b.storage_code,a.description,
                    compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, physical_inventory b
                    where a.stock_number = b.stock_number(+)
                    and a.stock_number = 'AC006'
                    union all
                    select a.stock_number, b.storage_code,a.description,
                    compute_qty_stored(b.warehouse_id,a.stock_number,b.storage_Code) qty_available
                    from stock_item a, storage_receipt b
                    where a.stock_number = b.stock_number(+)
                     and b.warehouse_id(+) like 'NLS%' and a.stock_number = 'AC006'
                                 )
             group by stock_number,storage_Code,qty_available)
             where seq <=4 and stock_number='AC006'
             group by stock_number
/

STOCK_NUMB LOC#1           QTY#1 LOC#2           QTY#2 LOC#3           QTY#3 LOC#4           QTY#4
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
AC006      ABC1                 0 ABC2                 0 ABC3                0 ABC4                
0

1 row selected.
 


Followup   September 10, 2003 - 8pm Central time zone:

look closely as your select list.

look at the decode on the seq.

you always use 1 for qty#
you use 1, 2, 3, 4 for the others. 

5 stars pivot   September 11, 2003 - 12pm Central time zone
Reviewer: mo 
Tom:

You are right thanks.

1.  The result set has four locations and four quantites.  If quantity is zero I do not want to 
print the location. Since I am printing this in an HTML table, I have to check for 4x3x2=24 
different possibilites so that the values will be placed starting the first cell. Is there a way 
easier than this? This is what I mean

If (qty1 <> 0 and qty2 <> 0 and qty3 <> 0 and qty4 <> 0) THEN
htp.p('<TD>'||loc1||</TD>');
htp.p('<TD>'||loc2||</TD>');
htp.p('<TD>'||loc3||</TD>');
htp.p('<TD>'||loc4||</TD>');
END IF;

IF (qty1 <> 0 and qty2 = 0 and ......)

2.  Instead of finding all quantities and then deciding not to print the quantities with "0" I 
modifed the query so that it will exculde those in the result set.

Problem here is that it exculded the stock numbers from the report and I wanted to show all of 
them?  Is there a wrokaround for this or I am stuck with option 1?

Thank you




 


5 stars Pivot analytic results including nulls   October 7, 2003 - 11am Central time zone
Reviewer: Robert from St Louis, Mo.
Tom,

We need a query to calculate the sum of invoices for a particular month by customer and product as 
well as the sum of invoices for a year ago.  This is what I have came up with so far:

create table t (month_and_year   date,
                customer         varchar2(20),
                product          varchar2(30),
                invoice          number)
/

insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'Central',      'Dog Chow',     
'100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'West',         'Dog Chow',     
'100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'East',         'Dog Chow',     
'100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'North',        'Dog Chow',     
'100');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'Central',      'Cat Chow',     
'50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'West',         'Cat Chow',     
'50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'East',         'Cat Chow',     
'50');
insert into t values (to_date('01-May-2002','DD-MON-YYYY'),     'North',        'Cat Chow',     
'50');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'Central',      'Dog Chow',     
'75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'West',         'Dog Chow',     
'75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'East',         'Dog Chow',     
'75');
insert into t values (to_date('01-Jun-2002','DD-MON-YYYY'),     'North',        'Dog Chow',     
'75');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'Central',      'Cat Chow',     
'25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'West',         'Cat Chow',     
'25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'East',         'Cat Chow',     
'25');
insert into t values (to_date('01-Jul-2002','DD-MON-YYYY'),     'North',        'Cat Chow',     
'25');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',      'Dog Chow',     
'200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',         'Dog Chow',     
'200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',         'Dog Chow',     
'200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',        'Dog Chow',     
'200');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',      'Cat Chow',     
'150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',         'Cat Chow',     
'150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',         'Cat Chow',     
'150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',        'Cat Chow',     
'150');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'Central',      'Beggin 
Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'West',         'Beggin 
Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'East',         'Beggin 
Strips','60');
insert into t values (to_date('01-May-2003','DD-MON-YYYY'),     'North',        'Beggin 
Strips','60');

select cy.month_and_year, 
       cy.customer, 
       cy.product, 
       cy.cy_invoices,
       ly.ly_invoices
  from (select month_and_year, 
               customer,
               product,
               SUM(invoice) over 
                  (partition by month_and_year, customer, product
                       order by month_and_year) cy_invoices
          from t) cy,
       (select month_and_year,
               customer,
               product, 
               SUM(invoice) over 
                  (partition by month_and_year, customer, product
                       order by month_and_year) ly_invoices
          from t) ly
 where trunc(cy.month_and_year,'MONTH') = add_months(trunc(sysdate,'MONTH'),-5)
   and trunc(cy.month_and_year,'MONTH') = add_months(trunc(ly.month_and_year,'MONTH'),+12)
   and (cy.customer = nvl(ly.customer,cy.customer) or ly.customer = nvl(cy.customer,ly.customer))
   and (cy.product = nvl(ly.product,cy.product) or ly.product = nvl(cy.product,ly.product))
 order by cy.cy_invoices desc


This returns:

MONTH_AND_YEAR      CUSTOMER     PRODUCT            CY_INVOICES LY_INVOICES
------------------- ------------ ------------------ ----------- -----------
2003-05-01 00:00:00 Central      Cat Chow                   150          50
2003-05-01 00:00:00 Central      Dog Chow                   200         100
2003-05-01 00:00:00 East         Cat Chow                   150          50
2003-05-01 00:00:00 East         Dog Chow                   200         100
2003-05-01 00:00:00 North        Cat Chow                   150          50
2003-05-01 00:00:00 North        Dog Chow                   200         100
2003-05-01 00:00:00 West         Cat Chow                   150          50
2003-05-01 00:00:00 West         Dog Chow                   200         100

8 rows selected.


I am missing the 4 rows for beggin strips because of missing year ago data.  

Can you point out what I am missing?

Thanks.

 


Followup   October 7, 2003 - 12pm Central time zone:

don't think you want analytics at all here -- just a simple pivot.  this creates a CY column of 
summed invoices for the data for the year we are in, and a LY column for last year.  Then, group by 
takes care of squishing out the extra rows by month.  

If you have many years of data -- the where clause would be different -- to include a where on the 
years of interest of course.

ops$tkyte@ORA920> select to_char( month_and_year, 'Mon' ),
  2         customer,
  3             product,
  4             sum( decode(to_char(month_and_year,'yyyy'), to_char(sysdate,'yyyy'), invoice, null 
) ) cy,
  5             sum( decode(to_char(month_and_year,'yyyy'), 
to_char(add_months(sysdate,-12),'yyyy'), invoice, null ) ) ly
  6    from t
  7   where to_char(month_and_year,'mon') = 'may'
  8   group by to_char( month_and_year, 'Mon' ), customer, product
  9  /
 
TO_ CUSTOMER             PRODUCT                                CY         LY
--- -------------------- ------------------------------ ---------- ----------
May East                 Cat Chow                              150         50
May East                 Dog Chow                              200        100
May East                 Beggin Strips                          60
May West                 Cat Chow                              150         50
May West                 Dog Chow                              200        100
May West                 Beggin Strips                          60
May North                Cat Chow                              150         50
May North                Dog Chow                              200        100
May North                Beggin Strips                          60
May Central              Cat Chow                              150         50
May Central              Dog Chow                              200        100
May Central              Beggin Strips                          60
 
12 rows selected.
 

5 stars Perfect   October 7, 2003 - 2pm Central time zone
Reviewer: Robert Ware from St Louis, Mo.
Tom,

Thats exactly what we needed.

Thanks! 


Followup   October 7, 2003 - 2pm Central time zone:

Oh -- one thing I wanted to add


THANK YOU
THANK YOU 
THANK YOU

for giving me a create table and insert into statements !!  you have no idea how much easier that 
is for me!

sometimes I get "nothing"

sometimes I get a describe (needs editing) and a select * from (needs LOTS of editing)

getting a ready to run script -- that's awesome. 

4 stars Let's upload files....   November 7, 2003 - 2pm Central time zone
Reviewer: Robert from CT
>>THANK YOU
>for giving me a create table and insert into >statements !!  you have no idea how 
>much easier that is for me!

you know, maybe you should consider allowing reader file-upload and like display an icon next to 
the message so the page is shorter

 


4 stars Consecutive Numbers   November 18, 2003 - 3pm Central time zone
Reviewer: Robert from USA
SQL> select MIN(c_year), MAX(c_year)
  2  from T
  3  ;

MIN(C_YEAR)   MAX(C_YEAR)
------------- --------------
1996          2003

There may be gaps in c_year value....How can I return
a list of c_year in CONSECUTIVE...
1996
1997
1998
1999
2000
2001
2002
2003


Thanks
 


Followup   November 21, 2003 - 11am Central time zone:

ops$tkyte@ORA920LAP> select min(c_year) min_c_year, max(c_year) max_c_year from t;

MIN_C_YEAR MAX_C_YEAR
---------- ----------
      1996       2003

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> select (select min(c_year) from t)+rownum-1
  2    from all_objects
  3   where rownum <= (select max(c_year)-min(c_year)+1 from t)
  4  /

(SELECTMIN(C_YEAR)FROMT)+ROWNUM-1
---------------------------------
                             1996
                             1997
                             1998
                             1999
                             2000
                             2001
                             2002
                             2003

8 rows selected.
 

5 stars Pivoting example using ref cursor   December 31, 2003 - 2pm Central time zone
Reviewer: David from SF, CA
Tom, in this posting above you say:
"If you have my book -- the last section of the analytic functions chapter shows how to pivot any 
query using a ref cursor and some simple query rewrite techniques"

I bought your book "Effective Oracle by Design" (enjoy it tremendously :D ) and looking at pages 
514-534 for this example but can't see it... Did I look into the wrong book?
 


Followup   December 31, 2003 - 3pm Central time zone:

at the point in time of this original posting, "my book" was unambigous -- there was only one.

Now there are three.... I was refering to Expert One on One Oracle. Here is a snippet to get you 
interested in the rest of that book:

.....
One other thing we must know is the MAXIMUM number of rows per partition we anticipate. This will 
dictate the number of columns we will be generating. Without it - we cannot pivot. SQL needs to 
know the number of columns and there is no way around that fact. That leads us into the next more 
generic  example of pivoting. If we do not know the number of total columns until runtime, we'll 
have to use dynamic SQL to deal with the fact that the SELECT list is variable. We can use PL/SQL 
to demonstrate how to do this - and end up with a generic routine that can be reused whenever you 
need a pivot. This routine will have the following specification:

scott@TKYTE816> create or replace package my_pkg
  2  as
  3      type refcursor is ref cursor;
  4      type array is table of varchar2(30);
  5
  6      procedure pivot( p_max_cols       in number   default NULL,
  7                       p_max_cols_query in varchar2 default NULL,
  8                       p_query          in varchar2,
  9                       p_anchor         in array,
 10                       p_pivot          in array,
 11                       p_cursor in out refcursor );
 12  end;
 13  /

Package created.

Here, you must send in either P_MAX_COLS or P_MAX_COLS_QUERY.  SQL needs to know the number of 
columns in a query and this parameter will allow us to build a query with the proper number of 
columns.  The value you should send in here will be the output of a query similar to:

scott@TKYTE816> select max(count(*)) from emp group by deptno, job;

That is - it is the count of the discrete values that are currently in ROWS that we will put into 
COLUMNS.  You can either send in the query to get this number, or the number if you already know 
it.

The P_QUERY parameter is simply the query that gathers your data together.  Using the last example 
from above the query would be:

 10    from (  select deptno, job, ename, sal,
 11                   row_number() over ( partition by deptno, job
 12                                           order by sal, ename ) rn
 13              from emp
 14             )

The next two inputs are arrays of column names.  The P_ANCHOR tells us what columns will stay CROSS 
RECORD (down the page) and P_PIVOT states the columns that will go IN RECORD (across the page).  In 
our example from above, P_ANCHOR = ( `DEPTNO', `JOB' ) and P_PIVOT = (`ENAME','SAL').  Skipping 
over the implementation for a moment, the entire call put together might look like this:

scott@TKYTE816> variable x refcursor
scott@TKYTE816> set autoprint on

scott@TKYTE816> begin
  2      my_pkg.pivot
  3      ( p_max_cols_query => 'select max(count(*)) from emp 
                                 group by deptno,job',
  4        p_query => 'select deptno, job, ename, sal,
  5                           row_number() over ( partition by deptno, job
  6                                               order by sal, ename ) rn
  7                      from emp a',
  8        p_anchor => my_pkg.array( 'DEPTNO','JOB' ),
  9        p_pivot  => my_pkg.array( 'ENAME', 'SAL' ),
 10        p_cursor => :x );
 11  end;
 12  /

PL/SQL procedure successfully completed.


DEPTNO JOB       ENAME_ SAL_1 ENAME_2    SAL_2 ENAME_3    SAL_3 ENAME_ SAL_4
------ --------- ------ ----- ---------- ----- ---------- ----- ------ -----
    10 CLERK     MILLER  1300
    10 MANAGER   CLARK   2450
    10 PRESIDENT KING    5000
    20 ANALYST   FORD    3000 SCOTT       3000
    20 CLERK     SMITH    800 ADAMS       1100
    20 MANAGER   JONES   2975
    30 CLERK     JAMES     99
    30 MANAGER   BLAKE     99
    30 SALESMAN  ALLEN     99 MARTIN        99 TURNER        99 WARD      99

9 rows selected.

As you can see - that dynamically rewrote our query using the generalized template we developed.  
The implementation of the package body is straightforward:

scott@TKYTE816> create or replace package body my_pkg
  2  as
  3
  4  procedure pivot( p_max_cols          in number   default NULL,
  5                   p_max_cols_query in varchar2 default NULL,
  6                   p_query          in varchar2,
  7                   p_anchor         in array,
  8                   p_pivot          in array,
  9                   p_cursor in out refcursor )
 10  as
 11      l_max_cols number;
 12      l_query    long;
 13      l_cnames   array;
 14  begin
 15      -- figure out the number of columns we must support
 16      -- we either KNOW this or we have a query that can tell us
 17      if ( p_max_cols is not null )
 18      then
 19          l_max_cols := p_max_cols;
 20      elsif ( p_max_cols_query is not null )
 21      then
 22          execute immediate p_max_cols_query into l_max_cols;
 23      else
 24          raise_application_error(-20001, 'Cannot figure out max cols');
 25      end if;
 26
 27
 28      -- Now, construct the query that can answer the question for us...
 29      -- start with the C1, C2, ... CX columns:
 30
 31      l_query := 'select ';
 32      for i in 1 .. p_anchor.count
 33      loop
 34          l_query := l_query || p_anchor(i) || ',';
 35      end loop;
 36
 37      -- Now add in the C{x+1}... CN columns to be pivoted:
 38      -- the format is "max(decode(rn,1,C{X+1},null)) cx+1_1"
 39
 40      for i in 1 .. l_max_cols
 41      loop
 42          for j in 1 .. p_pivot.count
 43          loop
 44              l_query := l_query ||
 45                  'max(decode(rn,'||i||','||
 46                              p_pivot(j)||',null)) ' ||
 47                              p_pivot(j) || '_' || i || ',';
 48          end loop;
 49      end loop;
 50
 51      -- Now just add in the original query
 52      l_query := rtrim(l_query,',')||' from ( '||p_query||') group by ';
 53
 54      -- and then the group by columns...
 55
 56      for i in 1 .. p_anchor.count
 57      loop
 58          l_query := l_query || p_anchor(i) || ',';
 59      end loop;
 60      l_query := rtrim(l_query,',');
 61
 62      -- and return it
 63      execute immediate 'alter session set cursor_sharing=force';
 64      open p_cursor for l_query;
 65      execute immediate 'alter session set cursor_sharing=exact';
 66  end;
 67
 68  end;
 69  /

Package body created.

It only does a little string manipulation to rewrite the query and open a REF CURSOR dynamically.  
In the likely event the query had a predicate with constants and such in it, we set cursor sharing 
on and then back off for the parse of this query to facilitate bind variables (see the section on 
tuning for more information on that).  Now we have a fully parsed query that is ready to be fetched 
from.
 

5 stars RE: Pivoting example using ref cursor   December 31, 2003 - 3pm Central time zone
Reviewer: David from SF, CA
Thank you Tom!
I thought Expert One-on-One Oracle is mostly conceptual book vs. Effective by Design which is 
mostly practical, but i guess I'll have to think again :)
6 stars as always ;)

David
 


5 stars Dear Tom, Happy New Year !   December 31, 2003 - 7pm Central time zone
Reviewer: Sami from NJ,USA
<asktom>
at the point in time of this original posting, "my book" was unambigous -- there 
was only one.
</asktom>

I thought FOUR
3)"Beginning Oracle Programming"
4)"Professional Oracle 8i Application Programming with Java, PL/SQL and XML "

 


Followup   December 31, 2003 - 7pm Central time zone:

I don't count 4) -- i had a single chapter in it :)   

3 stars Is this pivoting?   January 9, 2004 - 6pm Central time zone
Reviewer: Trevor Welch 
Hi Tom,

I have a table with say stock intradata

DATE        STOCK_CODE    LAST_PRICE  LAST_TRADE_TIME
01-JAN-2004 ABC           2.40        09:00
01-JAN-2004 ABC           2.41        09:05
01-JAN-2004 ABC           2.43        09:10
01-JAN-2004 ABC           2.39        10:00
01-JAN-2004 ABC           2.32        11:00
01-JAN-2004 ABC           2.41        11:31
01-JAN-2004 ABC           2.43        11:35
01-JAN-2004 ABC           2.40        12:00
01-JAN-2004 ABC           2.41        12:05

My requirement is for each half hour of the day to
be assigned a Character.
9:00 - 9:30   A
9:31 - 10:00  B
10:01 - 10:30 C
10:31 - 11:00 D
11:01 - 11:30 E
11:31 - 12:00 F
12:01 - 12:31 E
etc ... to 17:00

So the report required is like this

2.32 D
2.39 A
2.40 AF
2.41 AEF
2.43 F

Can you give me some pointers on how to do this please?

Thanks Trevor

 

 


Followup   January 10, 2004 - 10am Central time zone:

that is not "pivoting", that is aggregation.

questions -- why is there a 'date' and a 'last trade time'.  that is really just a single column in 
'real life' right.

Also, how do 2.40, 2.41, 2.43 become 2.39???? (letter A)

also, your time ranges are "strange"

9am sharp
but 10:01, 11:01 after that.

Hows about this

09:00 <= DT < 09:30 -> A
09:30 <= DT < 10:00 -> B
10:00 <= DT < 10:30 -> C

and so on...

And lastly, not getting that output in the least.  AF?  AEF??  huh whats up with that.

As i'll be away for a while you can start your endeavor using this technique:

ops$tkyte@ORA9IR2> select dt,
  2         trunc(to_char(dt,'sssss')/(30*60)),
  3             chr( ascii('A')+trunc(to_char(dt,'sssss')/(30*60))-18 )
  4    from t;
 
DT                TRUNC(TO_CHAR(DT,'SSSSS')/(30*60)) C
----------------- ---------------------------------- -
01-jan-2004 09:00                                 18 A
01-jan-2004 09:05                                 18 A
01-jan-2004 09:10                                 18 A
01-jan-2004 10:00                                 20 C
01-jan-2004 11:00                                 22 E
01-jan-2004 11:31                                 23 F
01-jan-2004 11:35                                 23 F
01-jan-2004 12:00                                 24 G
01-jan-2004 12:05                                 24 G
01-jan-2004 13:29                                 26 I
01-jan-2004 13:30                                 27 J
01-jan-2004 13:31                                 27 J
01-jan-2004 13:59                                 27 J
01-jan-2004 14:00                                 28 K
01-jan-2004 14:01                                 28 K
 
15 rows selected.


It gets your A, B, C assigned as per my method -- from there, i'm not sure what the logic is 

3 stars A bit more info   January 17, 2004 - 6am Central time zone
Reviewer: Trevor Welch 
Tom,

Thanks for your reply.

First of sorry, I didn't quite explain my requirements and there was an error in it so I'll have 
another go with
and actual screen capture of "Market Profile" in action

You are quite correct the Date column and last trade time are actually the same column.

I liked your technique however what I actually need is a "graph" of when the prices occured per 
each half hour eg

I am basing this request of the Chicago board of trade "Market Profile" Tool.

The Market Profile day is split into half-hour time periods, A is 0800 to 0830, 
B is 0830 to 0900, etc. 

Soybeans open at 0930 (D). In the profile below, the opening half hour ranged from 6664 to 6690.
The next period, E, covered 6680 to 6690; and so on for the rest of the day. 


SN     SOYBEANS (CBOT)         JUL 98 2-MAR-98
     6710    F      |   <== Upper Extreme price
     6704    F      | 
     6700    F      |
     6696    FG       | Upper Range Extension
     6694    EFG        |                 | 
     6690    DEFGH      |                 | Initial Balance
     6686    DEGH       |                 | (6694 to 6664)
     6684    DEGHIJ     | Value Area      | 
     6682    DEGHIJ     | 70% of Trade    | <== POC 6682
     6680    DEHIJK     | (6694 - 6672)   |
     6676    DHIJK      |                 |
     6674    DHIJK      |                 |
     6672    DHK        |                 |
     6670    DHK                          |
     6664    DK                           |
     6654    K      |
     6650    K      |
     6646    K      |
     6644    K      | Lower 
     6630    K      | Tail (single prints, 6654 - 6620)
     6624    K      |
     6620    K      |   <==Lower Extreme price


You said >>Also, how do 2.40, 2.41, 2.43 become 2.39???? (letter A)
2.40 2.41 2.43 don't become 2.39 they are assigned a letter representing each half hour period they 
occured in.

Does the above example make it any clearer?

Regards
Trevor

 


Followup   January 18, 2004 - 9am Central time zone:

not really -- a little -- but not really (i don't have a set of inputs, desired outputs).


sorry -- don't think I'll be able to answer your question here in the review/followup section -- 
when I'm taking quesitons -- go for it there, this is bigger than a breadbox. 

3 stars Pivot on columns from table   March 16, 2004 - 2pm Central time zone
Reviewer: Marcio from Brazil
Consider this:

create table t(
    p    VARCHAR2(1) constraint pk primary key,
    a        number(13,9),
    b        number(13,9),
    k        number(13,9)
);

insert into t values ('A', 10, 15.59040617, 5.493248938);
insert into t values ('B', 10.80674214, 16.71941596, 5.333903907);
insert into t values ('C', 10, 1516.238149, 14.64789478);
insert into t values ('D', 10, 15.59040617, 5.493248938);
insert into t values ('E', 10.80674214, 16.71941596, 5.333903907);
commit;

select 'a' p, 
       max(decode(p, 'A', a, null)) a,
       max(decode(p, 'B', a, null)) b,
       max(decode(p, 'C', a, null)) c,
       max(decode(p, 'D', a, null)) d,
       max(decode(p, 'E', a, null)) e
  from t
union
select 'b' p, 
       max(decode(p, 'A', b, null)) a,
       max(decode(p, 'B', b, null)) b,
       max(decode(p, 'C', b, null)) c,
       max(decode(p, 'D', b, null)) d,
       max(decode(p, 'E', b, null)) e
  from t
union
select 'k' p, 
       max(decode(p, 'A', k, null)) a,
       max(decode(p, 'B', k, null)) b,
       max(decode(p, 'C', k, null)) c,
       max(decode(p, 'D', k, null)) d,
       max(decode(p, 'E', k, null)) e
  from t
/

The result:
~~~~~~~~~~~

P          A          B          C          D          E
- ---------- ---------- ---------- ---------- ----------
a         10 10,8067421         10         10 10,8067421
b 15,5904062  16,719416 1516,23815 15,5904062  16,719416
k 5,49324894 5,33390391 14,6478948 5,49324894 5,33390391

The Question:
~~~~~~~~~~~~~
I would like know if is possible to do the query above access just once the table T.

The plan show me 3 fts.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (UNIQUE)
   2    1     UNION-ALL
   3    2       SORT (AGGREGATE)
   4    3         TABLE ACCESS (FULL) OF 'T'
   5    2       SORT (AGGREGATE)
   6    5         TABLE ACCESS (FULL) OF 'T'
   7    2       SORT (AGGREGATE)
   8    7         TABLE ACCESS (FULL) OF 'T'

Thank you,
Marcio 


Followup   March 16, 2004 - 2pm Central time zone:

ops$tkyte@ORA9IR2> set autotrace on explain
ops$tkyte@ORA9IR2> select decode( x, 1, 'a', 2, 'b', 3, 'k' ) pp,
  2         max(decode(p,'A',decode(x,1,a,2,b,k))) a,
  3         max(decode(p,'B',decode(x,1,a,2,b,k))) b,
  4         max(decode(p,'C',decode(x,1,a,2,b,k))) c,
  5         max(decode(p,'D',decode(x,1,a,2,b,k))) d,
  6         max(decode(p,'E',decode(x,1,a,2,b,k))) e
  7    from t, (select 1 x from dual union all
  8             select 2 x from dual union all
  9             select 3 x from dual )
 10   group by decode( x, 1, 'a', 2, 'b', 3, 'k' )
 11  /
 
P          A          B          C          D          E
- ---------- ---------- ---------- ---------- ----------
a         10 10.8067421         10         10 10.8067421
b 15.5904062  16.719416 1516.23815 15.5904062  16.719416
k 5.49324894 5.33390391 14.6478948 5.49324894 5.33390391
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (GROUP BY)
   2    1     NESTED LOOPS
   3    2       VIEW
   4    3         UNION-ALL
   5    4           TABLE ACCESS (FULL) OF 'DUAL'
   6    4           TABLE ACCESS (FULL) OF 'DUAL'
   7    4           TABLE ACCESS (FULL) OF 'DUAL'
   8    2       TABLE ACCESS (FULL) OF 'T'
 

5 stars Thank you   March 17, 2004 - 8am Central time zone
Reviewer: Marcio from Brazil


2 stars Still Confused How to Apply it   April 2, 2004 - 3pm Central time zone
Reviewer: Mary W from VA
i have a resutls set that looks lieke this:

email    qNumber     letterSequence      Text
m@m.com  1           a                   null
m@m.com  2           d                   null
t@t.com  1           c                   null
t@t.com  1           b                   null
t@t.com  2           a                   txt
t@t.com  2           d                   null


i need the RS to look like:
email   q1a      q1b   q1c    q2a     q2b   q2c     q2d  
m@m.com  a                                           d
t@t.com  c        b            txt                   d 


Followup   April 2, 2004 - 3pm Central time zone:

select email, 
       max( decode( qnumber, 1, decode(letterSequence,'a','a') ) ) q1a,
       max( decode( qnumber, 1, decode(letterSequence,'b','b') ) ) q1b,
       max( decode( qnumber, 1, decode(letterSequence,'c','c') ) ) q1c,
.... same for 2 ....
 from t
 group by email; 

5 stars WOW! You are a the best!   April 12, 2004 - 8am Central time zone
Reviewer: A reader 
Thank you so very much!  it works great!

--mary 


2 stars How can we generate rows?   April 21, 2004 - 9am Central time zone
Reviewer: Dhrubo from Kolkata,Dhrubo
Hi Tom,
      I have a select as given below :

SELECT START_DATE,END_DATE FROM SOME_TABLE WHERE SOME_ID = :A

Now SOME_ID being the primary key for SOME_TABLE, will return one row like
START_DATE         END_DATE
----------         --------
12/06/2003         12/10/2003

Note that the difference between the start date and end date is 5 days.

So i want an output like this :
DAY             DATE
---             ----
1               12/06/2003
2               12/07/2003
3               12/08/2003
4               12/09/2003
5               12/10/2003

How can i achieve this result , can you please help
Thanks in advance

 


Followup   April 21, 2004 - 8pm Central time zone:

ops$tkyte@ORA9IR2> select rn day, a+rn-1 dt
  2    from (select a,b from t where id = 1),
  3         (select rownum rn
  4            from all_objects
  5           where rownum <= (select ceil(b-a)+1 from t where id = 1))
  6  /
 
       DAY DT
---------- ---------
         1 06-DEC-03
         2 07-DEC-03
         3 08-DEC-03
         4 09-DEC-03
         5 10-DEC-03
 

4 stars Collapsing null columns into 1 row using a pivot query.   April 22, 2004 - 6pm Central time zone
Reviewer: ht from california
Tom,
Is there a way to modify the query below to produce results that look like this?

deptno isclerk ismanager ispresident
10     Miller  Clark     King

  1  select deptno,
  2  ( decode( val, 'CLERK', ename, null ) )isclerk,
  3  ( decode( val, 'MANAGER', ename, null ) )ismanager,
  4  ( decode( val, 'SALESMAN', ename, null ) ) issalesman,
  5  ( decode( val, 'ANALYST', ename, null ) ) isanalyst,
  6  ( decode( val, 'PRESIDENT', ename, null ) ) ispresident
  7  from
  8     (
  9     select deptno,job val,ename
 10     from scott.emp
 11     where deptno=10
 12     group by deptno,job,ename
 13     )
 14* group by deptno,val,ename
 15  /

    DEPTNO ISCLERK    ISMANAGER  ISSALESMAN ISANALYST  ISPRESIDEN
---------- ---------- ---------- ---------- ---------- ----------
        10 MILLER
        10            CLARK
        10                                             KING

3 rows selected.

>
 


Followup   April 23, 2004 - 10am Central time zone:

what do you want to have happen when there are 10 clerks in a single deptno? 

5 stars   April 22, 2004 - 8pm Central time zone
Reviewer: A reader 
Tom,
I figured it out:

select
max( decode( job, 'CLERK', ename ) )isclerk,
max( decode( job, 'MANAGER', ename) )ismanager,
max( decode( job, 'PRESIDENT', ename ) ) ispresident
from ( select deptno,job,ename from scott.emp where deptno=10 ) 


Followup   April 23, 2004 - 10am Central time zone:

i'll do the yahbut here...

change 10 to 20 -- now what sense does the "isclerk" field make. 

5 stars   April 23, 2004 - 1pm Central time zone
Reviewer: A reader 
Hi Tom,
You're correct, if more than 1 clerk exists in dept 10, this won't work.  My data guarantees no 
duplicates - I just used the scott.emp table as an example since it would (I had hoped) be a better 
starting point.  Sorry about that.
ht 


5 stars   June 7, 2004 - 12pm Central time zone
Reviewer: A reader 
Tom, please help, it's urgent
this pivot query works fine:
select item_code a, sum(decode(cSize,'small',cnt,0)) b,
                    sum(decode(cSize,'medium',cnt,0)) c,
                    sum(decode(cSize,'large',cnt,0)) d                    
from(
select item_code, decode(substr(item_code,8,3),
                  'S','small',
                  'M','medium',
                  'L','large' ) as cSize,
sum(NVL(BAL_ON_HAND,0)-NVL(OPEN_ORDERs,0)) as cnt
from inventory
where account_id=45
group by item_code, decode(substr(item_code,8,3),
                  'S','small',
                  'M','medium',
                  'L','large' ) 
)
group by item_code
order by a;
When I added substr to item_code I got error:
ERROR at line 17:
ORA-00904: "ITEM_CODE": invalid identifier
my query is:
select substr(item_code,1,instr(item_code,'-',1,2)-1) a, sum(decode(cSize,'small',cnt,null)) b,
                                                         sum(decode(cSize,'medium',cnt,null)) c,
                                                         sum(decode(cSize,'large',cnt,null)) d
from(
select substr(item_code,1,instr(item_code,'-',1,2)-1) , 
decode(substr(item_code,instr(item_code,'-',1,2)+1,3),
                                                        'S','small',
                                                        'M','medium',
                                                        'L','large' ) as cSize,
sum(NVL(BAL_ON_HAND,0)-NVL(OPEN_ORDERs,0)) as cnt
from inventory
where account_id=45
group by 
substr(item_code,1,instr(item_code,'-',1,2)-1),decode(substr(item_code,instr(item_code,'-',1,2)+1,3)
,
                                                        'S','small',
                                                        'M','medium',
                                                        'L','large' ) 
)
group by substr(item_code,1,instr(item_code,'-',1,2)-1)
order by a;
thank you. 


Followup   June 7, 2004 - 1pm Central time zone:

rom(
select substr(item_code,1,instr(item_code,'-',1,2)-1) ITEM_CODE, 
decode(substr(item_code,instr(item_code,'-',1,2)+1,3),
                                                        'S','small',
                                                        'M','medium',
                                                        'L','large' ) as cSize, 

4 stars Good reminder   June 15, 2004 - 5am Central time zone
Reviewer: velvet from Warsaw, Poland
I love this page. Just can't remember this trick and when it's needed I just type in 'pivot table' 
in the site search and thats it! 


5 stars what is wrong in the following pivot?   June 23, 2004 - 1pm Central time zone
Reviewer: A reader 
I am missing something simple. Trying to pivot
columns into rows...
----
scott@ORA10G> drop table t1;

Table dropped.

scott@ORA10G> create table t1
  2  (
  3    x number,
  4    y number,
  5    z number
  6  );

Table created.

scott@ORA10G> 
scott@ORA10G> insert into t1 values( 1, 2, 3 );

1 row created.

scott@ORA10G> insert into t1 values( 2, 2, 10 );

1 row created.

scott@ORA10G> insert into t1 values( 2, 2, 9 );

1 row created.

scott@ORA10G> insert into t1 values( 3, 5, 5 );

1 row created.

scott@ORA10G> commit;

Commit complete.

scott@ORA10G> 
scott@ORA10G> select * from t1;

   X    Y    Z
---- ---- ----
   1    2    3
   2    2   10
   2    2    9
   3    5    5

scott@ORA10G> 
scott@ORA10G> column x format 999
scott@ORA10G> column y format 999
scott@ORA10G> column z format 999
scott@ORA10G> column avg_x format 999
scott@ORA10G> column avg_y format 999
scott@ORA10G> column avg_z format 999
scott@ORA10G> 
scott@ORA10G> select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  2  from t1;

AVG_X AVG_Y      MAX_X      MAX_Y
----- ----- ---------- ----------
    2     3          3          5

scott@ORA10G> /* I want the output to be
scott@ORA10G>   Column Average Max
scott@ORA10G>   'x'    2        3
scott@ORA10G>   'y'    3        5
scott@ORA10G> */
scott@ORA10G> 
scott@ORA10G> /* First I create the select to pivot columns into rows */
scott@ORA10G> 
scott@ORA10G>   select avg_x, avg_y, max_x, max_y , r
  2    from
  3    (
  4       select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  5       from t1
  6    ) a,
  7    ( select rownum r from all_objects where rownum <= 4 );

AVG_X AVG_Y      MAX_X      MAX_Y          R
----- ----- ---------- ---------- ----------
    2     3          3          5          1
    2     3          3          5          2
    2     3          3          5          3
    2     3          3          5          4

scott@ORA10G> 
scott@ORA10G> /* Next I use decode to pivot */
scott@ORA10G> select decode(r, 1, avg_x, 2, avg_y, 3, max_x, 4, max_y )  details
  2  from
  3  (
  4    select avg_x, avg_y, max_x, max_y , r
  5    from
  6    (
  7       select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  8       from t1
  9    ) a,
 10    ( select rownum r from all_objects where rownum <= 4 )
 11  );

   DETAILS
----------
         2
      2.75 <-----should have got avg_y = 3 here?
         3
         5

-----

I was expecting 3 (avg_y) above instead of 2.75?
What went wrong?
 


Followup   June 23, 2004 - 1pm Central time zone:

column details format 999


you formatted x,y,z avg_y and so on with a 999 mask.

you didn't do that for DETAILS

the real average is 2.75, 3 is the rounded off one.

 

5 stars thanx!!!   June 23, 2004 - 1pm Central time zone
Reviewer: A reader 


5 stars ok here is my solution for the pivot problem   June 23, 2004 - 2pm Central time zone
Reviewer: A reader 
This is refering back to q above where
I wanted an output as
---
col_name  average max
'x'       2.00    3.00
'y'       2.75    5.00

The select at the end does it for me - but
is there a more elegant solution?

thanx!
scott@ORA10G> drop table t1;

Table dropped.

scott@ORA10G> create table t1
  2  (
  3    x number,
  4    y number,
  5    z number
  6  );

Table created.

scott@ORA10G> 
scott@ORA10G> insert into t1 values( 1, 2, 3 );

1 row created.

scott@ORA10G> insert into t1 values( 2, 2, 10 );

1 row created.

scott@ORA10G> insert into t1 values( 2, 2, 9 );

1 row created.

scott@ORA10G> insert into t1 values( 3, 5, 5 );

1 row created.

scott@ORA10G> commit;

Commit complete.

scott@ORA10G> 
scott@ORA10G> select * from t1;

   X    Y    Z
---- ---- ----
   1    2    3
   2    2   10
   2    2    9
   3    5    5

scott@ORA10G> 
scott@ORA10G> column x format 999
scott@ORA10G> column y format 999
scott@ORA10G> column z format 999
scott@ORA10G> column avg_x format 999.99
scott@ORA10G> column avg_y format 999.99
scott@ORA10G> column avg_z format 999.99
scott@ORA10G> column average format 999.99
scott@ORA10G> column max format 999.99
scott@ORA10G> column details format 999.99
scott@ORA10G> 
scott@ORA10G> select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  2  from t1;

  AVG_X   AVG_Y      MAX_X      MAX_Y
------- ------- ---------- ----------
   2.00    2.75          3          5

scott@ORA10G> /* I want the output to be
scott@ORA10G>   Column Average Max
scott@ORA10G>   'x'    2.00    3
scott@ORA10G>   'y'    2.75    5
scott@ORA10G> */
scott@ORA10G> 
scott@ORA10G> /* First I create the select to pivot columns into rows */
scott@ORA10G> 
scott@ORA10G>   select avg_x, avg_y, max_x, max_y , r
  2    from
  3    (
  4       select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  5       from t1
  6    ) a,
  7    ( select rownum r from all_objects where rownum <= 4 );

  AVG_X   AVG_Y      MAX_X      MAX_Y          R
------- ------- ---------- ---------- ----------
   2.00    2.75          3          5          1
   2.00    2.75          3          5          2
   2.00    2.75          3          5          3
   2.00    2.75          3          5          4

scott@ORA10G> 
scott@ORA10G> /* Next I use decode to pivot */
scott@ORA10G> select decode(r, 1, avg_x ) average, decode( r, 2, max_x) max,
  2          decode(r, 3, avg_y) average, decode( r, 4, max_y) max
  3  from
  4  (
  5    select avg_x, avg_y, max_x, max_y , r
  6    from
  7    (
  8       select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  9       from t1
 10    ) a,
 11    ( select rownum r from all_objects where rownum <= 4 )
 12  );

AVERAGE     MAX AVERAGE     MAX
------- ------- ------- -------
   2.00
           3.00
                   2.75
                           5.00

scott@ORA10G> 
scott@ORA10G> select * from
  2  (
  3  with common_query as
  4  (
  5       select avg_x, avg_y, max_x, max_y , r
  6    from
  7    (
  8       select avg(x) avg_x, avg(y) avg_y, max(x) max_x, max(y) max_y
  9       from t1
 10    ) a,
 11    ( select rownum r from all_objects where rownum <= 4 )
 12  )
 13  select col_name, max(average) average, max(max) max
 14  from
 15  (
 16    select  case when r = 1 or r = 2 then 'x' end col_name,
 17           decode(r, 1, avg_x ) average, decode( r, 2, max_x) max
 18    from common_query
 19    union
 20    select case when r = 3 or r = 4 then 'y' end col_name,
 21        decode(r, 3, avg_y) average, decode( r, 4, max_y) max
 22    from common_query
 23  )
 24  group by col_name
 25  )
 26  where col_name is not null;

C AVERAGE     MAX
- ------- -------
x    2.00    3.00
y    2.75    5.00

scott@ORA10G> 
 


Followup   June 23, 2004 - 2pm Central time zone:

ops$tkyte@ORA9IR2> select decode( r, 1, 'X', 2, 'Y', 'Z' ) col,
  2         decode( r, 1, avg_x, 2, avg_y, 3, avg_z ) avg,
  3         decode( r, 1, max_x, 2, max_y, 3, max_z ) max
  4    from ( select max(x) max_x, avg(x) avg_x,
  5                  max(y) max_y, avg(y) avg_y,
  6                  max(z) max_z, avg(z) avg_z
  7             from t1
  8             ),
  9         (select rownum r from all_objects where rownum <= 3
 10         )
 11  /
 
C        AVG        MAX
- ---------- ----------
X          2          3
Y       2.75          5
Z       6.75         10
 

5 stars :)   June 23, 2004 - 2pm Central time zone
Reviewer: A reader 
my solution was almost embarrasingly complicated
compared to yours:)

thanx! 


5 stars Thank you! Just what I needed   July 1, 2004 - 2pm Central time zone
Reviewer: Reena from CO, USA
Extremely useful.  Loved the explaination about the cartesian product posted by someone above.

Thank you, Tom! 


3 stars From a pivot table....   July 22, 2004 - 10am Central time zone
Reviewer: mary W from arlington , va
Tom,

I have a table with c_data

select * from c_data;

   CHAN_ID       CH_1       CH_4       CH_5       CH_6
---------- ---------- ---------- ---------- ----------
         1        100                   100        100
         4                   100         78         60
         5        .79      30.95        100      69.05


I need to get this data in the following form:
CHAN_ID       REL_CHAN_ID      RANK
------------  --------------  ---------
1                 1              100
1                 4             
1                 5              100
1                 6              100
4                 1
4                 4              100
4                 5              78
4                 6              60
etc.....

How can I un-pivot table c_data?

Thanks in advance.
 


Followup   July 23, 2004 - 7am Central time zone:

no create table...
no insert into....

so no tested answer -- just an off the cuff on, might run, might not (your review looks just like 
the example i use in my review page - the one that says "DON'T DO THIS, don't give me this 
information"):

   CHAN_ID       CH_1       CH_4       CH_5       CH_6

select chan_id,
       decode( r, 1, 1, 2, 4, 3, 5, 4, 6 ) rel_chan,
       decode( r, 1, ch_1, 2, ch_4, 3, ch_5, 4, ch_6 ) rank
  from t, 
       (select rownum r from all_objects where rownum <= 4 )

 

4 stars I found one solution but is there an easier way?   July 22, 2004 - 1pm Central time zone
Reviewer: mary W from arlington va
Here is my solution:
select chan_id, chan_rel_id, score 

from 
(
    select chan_id, decode(ch_1, ch_1, 1) as chan_rel_id, ch_1 as score  from masha_test
    union all
    select chan_id, decode(ch_4, ch_4, 4), ch_4 from masha_test
    union all
    select chan_id, decode(ch_5, ch_5, 5), ch_5 from masha_test
    union all
    select chan_id, decode(ch_6, ch_6, 6), ch_6 from masha_test
)

order by chan_id 

But i have a total of 68 chan_id and there fore total of 68 columns of ch_1 - ch_68  .  Is there an 
easier way?
 


Followup   July 23, 2004 - 8am Central time zone:

decode.... see above.

 

5 stars Its increadible!   July 23, 2004 - 9am Central time zone
Reviewer: Mary W from arlington, va
Tom, 

Thank you so very much. Your solution is simple, elegant, and genious!

mary 


4 stars is there a limitation in decode?   August 3, 2004 - 11am Central time zone
Reviewer: Ajeet from Mumbai - India
<code>Hi Tom,
I have a table which has 200 colums , I need to convert these columns into row -- 3 columns will not be pivot--and 107 would be --query I wrote is simply taken from your answers above --and this works fine if I give 100 or so values in decode statement below..but If use 300 values it give me an error --
ERROR at line 2:
ORA-00939: too many arguments for function --

so my question is there any limitaiton in decode or I am doing something wrong. I tried with 255 values..in that case also I got an error.If there is a limitation or sqlplus formating required please let me know.also in case of limitation is there any work around ?

Thanks again

--sorry about pasting this ugly query
as it will take lots of spac---but I think you may ask for it in order to give me an answer.

select fleet,aircraft_id,engine_id,flight_datetime,install_datetime,
decode(r,1,PIP_BLED, 2,CORETA_SMOOTHED, 3,BPENET_SMOOTHED, 4,ADJCEQ_SMOOTHED,
5,WF36,6,T5SE,7,T5,8,T49SE,9,T495SE,10,T495,11,T3SE,12,T3,13,T25SE,14,T25,15,SWF36,
16,SUMN25,17,SOL25,18,SOL2,19,SOL12,20,SHDEGT,21,SFCLPT,22,SFCLPC,23,SFCHTS,24,SFCHTR,
25,SFCHPC,26,SFCFAN,27,SCRSFC,28,PSB,29,PS25,30,PS15,31,PS13,32,PRREF,33,PIP_MPA,
34,P495,35,P48,36,P25,37,P15,38,GXN25,39,GWF36,40,GT5,41,GT49C,42,GT495C,43,GT495,
44,GT49,45,GT3,46,GT25,47,GPS3,48,GPS25,49,GPS14,50,GPS13,51,GP49,52,GP25,53,ETA,54,EGTLPT,
55,EGTLPC,56,EGTHTS,57,EGTHTR,58,EGTHPC,59,EGTFAN,60,DLPTFL,61,DLPTEF,62,DLPCFL,63,DLPCEF,
64,DHPTFL,65,DHPTEF,66,DHPCFL,67,DHPCEF,68,DFANFL,69,DFANEF,70,CORTPR,71,CORTAP,72,CORETA,
73,BPENET,74,ADJCEQ,75,CCCVPOS_D,76,WFPS3_SMOOTHED,77,WFPS3,78,OILFDP_SMOOTHED,
79,EGTDELTA_SMOOTHED,80,DELFN_SMOOTHED,81,ZVBV_D,82,LPTCPOS_D,83,HPTCPOS_D,
84,GWFR_D,85,GN2R_D,86,TIMESTAMP,87,ZPCN12_D_SMOOTHED,88,ZPCN12_D,89,ZVB2F_SMOOTHED,
90,ZVB1R_SMOOTHED,91,EDITED_IND,92,SAGE_ENG_OUTPUT_SEQ_NUM,93,MAINT_WORK_ORDER,
94,PN2ETM_DETER,95,HDEGTM_DETER,96,EGTETM_DETER,97,GPCN25_DETER,98,GWFM_DETER,
99,DEGT_DETER,100,ZWF36_D_SMOOTHED,101,ZVB2R_SMOOTHED,102,ZVB1F_SMOOTHED,
103,ZT49_D_SMOOTHED,104,ZTOIL_SMOOTHED,105,ZTNAC_D_SMOOTHED,106,ZPOIL_SMOOTHED,
107,ZPCN25_D_SMOOTHED,108,SLOATL_SMOOTHED,109,PN2MAR_SMOOTHED,
110,PN2ETM_SMOOTHED,111,GWFR_SMOOTHED,112,GWFM_D_SMOOTHED,113,GWFM_SMOOTHED,
114,EGTHDM_SMOOTHED,115,GPCN25_SMOOTHED,116,GPCN25_D_SMOOTHED,
117,GN2MC_SMOOTHED,118,GEGTMC_SMOOTHED,119,GN2R_SMOOTHED,120,EGTHDM_D_SMOOTHED,
121,ZTNAC_SMOOTHED,122,CWFPADJ2_LT_SMOOTHED,123,CADJCEQ2_LT_SMOOTHED,
124,DELN12_LT_SMOOTHED,125,DELFN2_LT_SMOOTHED,126,ZVB2R2_LT_SMOOTHED,
127,ZVB2F2_LT_SMOOTHED,128,ZVB1R2_LT_SMOOTHED,129,ZVB1F2_LT_SMOOTHED,
130,ZTOIL2_LT_SMOOTHED,131,ZPOIL2_LT_SMOOTHED,132,GWFR3_LT_SMOOTHED,
133,GWFR2_LT_SMOOTHED,134,GN2R3_LT_SMOOTHED,135,GN2R2_LT_SMOOTHED,
136,DPOIL2_LT_SMOOTHED,137,GWFM3_LT_SMOOTHED,138,GWFM2_LT_SMOOTHED,
139,CGPCN253_LT_SMOOTHED,140,CGPCN252_LT_SMOOTHED,141,CEGTHDM2_LT_SMOOTHED,
142,DEGT3_LT_SMOOTHED,143,DEGT2_LT_SMOOTHED,144,CWFPADJ2_SMOOTHED,
145,CADJCEQ2_SMOOTHED,146,DELN12_SMOOTHED,147,DELFN2_SMOOTHED,
148,ZVB2R2_SMOOTHED,149,ZVB2F2_SMOOTHED,150,ZVB1R2_SMOOTHED,
151,ZVB1F2_SMOOTHED,152,ZTOIL2_SMOOTHED,153,ZPOIL2_SMOOTHED,
154,GWFR3_SMOOTHED,155,GWFR2_SMOOTHED,156,GN2R3_SMOOTHED,157,GN2R2_SMOOTHED,
158,DPOIL2_SMOOTHED,159,GWFM3_SMOOTHED,160,GWFM2_SMOOTHED,161,CGPCN253_SMOOTHED,
162,CGPCN252_SMOOTHED,163,CEGTHDM2_SMOOTHED,164,DEGT3_SMOOTHED,165,DEGT2_SMOOTHED,
166,CWFPADJ2,167,CADJCEQ2,168,DELN12,169,DELFN2,170,GWFR3,171,GWFR2,172,GN2R3,
173,GN2R2,174,DPOIL2,175,GWFM3,176,GWFM2,177,CGPCN253,178,CGPCN252,179,CEGTHDM2,
180,DEGT3,181,DEGT2,182,DELN1_LT_SMOOTHED,183,DELFN_LT_SMOOTHED,184,WFPADJ_LT_SMOOTHED,
185,ADJCEQ_LT_SMOOTHED,186,ZVB2R_LT_SMOOTHED,187,ZVB2F_LT_SMOOTHED,188,ZVB1R_LT_SMOOTHED,
189,ZVB1F_LT_SMOOTHED,190,ZTOIL_LT_SMOOTHED,191,ZPOIL_LT_SMOOTHED,192,GWFR_LT_SMOOTHED,
193,GWFM_LT_SMOOTHED,194,EGTHDM_LT_SMOOTHED,195,GPCN25_LT_SMOOTHED,196,GN2R_LT_SMOOTHED,
197,DPOIL_LT_SMOOTHED,198,DEGT_LT_SMOOTHED,199,ZVB2RCF6_SMOOTHED,200,TAT_ENG_SMOOTHED,201,FIRELT2_SMOOTHED,
202,FIRELT1_SMOOTHED,203,EGT_TOR_SMOOTHED,204,CHIPCBC_SMOOTHED,205,T25_SMOOTHED,206,FF_DELP_SMOOTHED,
207,EGTPL_SMOOTHED,208,EGTPH_SMOOTHED,209,EGT4_SMOOTHED,210,EGT3_SMOOTHED,211,EGT2_SMOOTHED,
212,EGT1_SMOOTHED,213,CHIPBC_SMOOTHED,214,EXCLUDE_FROM_DERATE,215,WFPREF_SMOOTHED,
216,WFPREF,217,WFPADJ_SMOOTHED,218,WFPDEL_SMOOTHED,219,WFPACT_SMOOTHED,220,DEVIAT_SMOOTHED,
221,WFPADJ,222,WFPDEL,223,WFPACT,224,DEVIAT,225,PRREF_SMOOTHED,226,ETA_SMOOTHED,227,CORTPR_SMOOTHED,
228,CORTAP_SMOOTHED,229,EGTETM_SMOOTHED,230,DPOIL_SMOOTHED,231,DELVSV_SMOOTHED,
232,DEGT_D_SMOOTHED,233,DEGT_SMOOTHED,234,ZTNAC_D,235,VSVNOM,236,WBE,237,WFMP,
238,WBI,239,ZT49_D,240,SLOATL_D,241,PIP_DRAT,242,PIP_CLET,243,PIP_XN1C,244,PIP_VLTO,
245,PIP_VLCR,246,PIP_VLCL,247,PIP_SLTL,248,PIP_PEM,249,PIP_OILM,250,PIP_ETOP,
251,PIP_DVSV,252,PIP_CLDR,253,PCN1BR,254,PN2MAR_D,255,PN2MAR,256,PCN2C,257,PCN1K,
258,PN2ETM,259,PCN1AR,260,PCN12I,261,PCN12,262,IAIWG,263,IAIE,264,GWFR,265,GWFM_D,266,GWFM,
267,GPCN25_D,268,GPCN25,269,GN2R,270,GN2MC,271,GEGTMC,272,ETSI,273,ETSV,274,ETSN,275,EGTHDM_D,276,EGTHDM,
277,EGTETM,278,EGTC,279,ECSI,280,ECSV,281,ECSN,282,DPOIL,283,DIVWFE,284,DIVTLA,285,DIVNAC,286,DIVN2,287,DIVEGT,
288,DELVSV,289,DELN1,290,DELFN,291,DEGT,292,DEGT_D,293,BRAT,294,ALERT_COUNT_OTHER,295,ALERT_COUNT_VIBES,
296,ALERT_COUNT_OIL,297,ALERT_COUNT_PERFORMANCE,298,ALERT_COUNT,299,ENGINE_POSITION,300,FLIGHT_PHASE,
302,GPCN12,303,DELEPR,316,IAIE,445,SLOATL,446,ZPCN25_D,447,ZTLA_D,448,ZWF36_D ) param_value
from (select rownum r from all_objects where rownum <= 307),
flt_sage_eng_out_load
where
decode(r,1,PIP_BLED, 2,CORETA_SMOOTHED, 3,BPENET_SMOOTHED, 4,ADJCEQ_SMOOTHED,
5,WF36,6,T5SE,7,T5,8,T49SE,9,T495SE,10,T495,11,T3SE,12,T3,13,T25SE,14,T25,15,SWF36,
16,SUMN25,17,SOL25,18,SOL2,19,SOL12,20,SHDEGT,21,SFCLPT,22,SFCLPC,23,SFCHTS,24,SFCHTR,
25,SFCHPC,26,SFCFAN,27,SCRSFC,28,PSB,29,PS25,30,PS15,31,PS13,32,PRREF,33,PIP_MPA,
34,P495,35,P48,36,P25,37,P15,38,GXN25,39,GWF36,40,GT5,41,GT49C,42,GT495C,43,GT495,
44,GT49,45,GT3,46,GT25,47,GPS3,48,GPS25,49,GPS14,50,GPS13,51,GP49,52,GP25,53,ETA,54,EGTLPT,
55,EGTLPC,56,EGTHTS,57,EGTHTR,58,EGTHPC,59,EGTFAN,60,DLPTFL,61,DLPTEF,62,DLPCFL,63,DLPCEF,
64,DHPTFL,65,DHPTEF,66,DHPCFL,67,DHPCEF,68,DFANFL,69,DFANEF,70,CORTPR,71,CORTAP,72,CORETA,
73,BPENET,74,ADJCEQ,75,CCCVPOS_D,76,WFPS3_SMOOTHED,77,WFPS3,78,OILFDP_SMOOTHED,
79,EGTDELTA_SMOOTHED,80,DELFN_SMOOTHED,81,ZVBV_D,82,LPTCPOS_D,83,HPTCPOS_D,
84,GWFR_D,85,GN2R_D,86,TIMESTAMP,87,ZPCN12_D_SMOOTHED,88,ZPCN12_D,89,ZVB2F_SMOOTHED,
90,ZVB1R_SMOOTHED,91,EDITED_IND,92,SAGE_ENG_OUTPUT_SEQ_NUM,93,MAINT_WORK_ORDER,
94,PN2ETM_DETER,95,HDEGTM_DETER,96,EGTETM_DETER,97,GPCN25_DETER,98,GWFM_DETER,
99,DEGT_DETER,100,ZWF36_D_SMOOTHED,101,ZVB2R_SMOOTHED,102,ZVB1F_SMOOTHED,
103,ZT49_D_SMOOTHED,104,ZTOIL_SMOOTHED,105,ZTNAC_D_SMOOTHED,106,ZPOIL_SMOOTHED,
107,ZPCN25_D_SMOOTHED,108,SLOATL_SMOOTHED,109,PN2MAR_SMOOTHED,
110,PN2ETM_SMOOTHED,111,GWFR_SMOOTHED,112,GWFM_D_SMOOTHED,113,GWFM_SMOOTHED,
114,EGTHDM_SMOOTHED,115,GPCN25_SMOOTHED,116,GPCN25_D_SMOOTHED,
117,GN2MC_SMOOTHED,118,GEGTMC_SMOOTHED,119,GN2R_SMOOTHED,120,EGTHDM_D_SMOOTHED,
121,ZTNAC_SMOOTHED,122,CWFPADJ2_LT_SMOOTHED,123,CADJCEQ2_LT_SMOOTHED,
124,DELN12_LT_SMOOTHED,125,DELFN2_LT_SMOOTHED,126,ZVB2R2_LT_SMOOTHED,
127,ZVB2F2_LT_SMOOTHED,128,ZVB1R2_LT_SMOOTHED,129,ZVB1F2_LT_SMOOTHED,
130,ZTOIL2_LT_SMOOTHED,131,ZPOIL2_LT_SMOOTHED,132,GWFR3_LT_SMOOTHED,
133,GWFR2_LT_SMOOTHED,134,GN2R3_LT_SMOOTHED,135,GN2R2_LT_SMOOTHED,
136,DPOIL2_LT_SMOOTHED,137,GWFM3_LT_SMOOTHED,138,GWFM2_LT_SMOOTHED,
139,CGPCN253_LT_SMOOTHED,140,CGPCN252_LT_SMOOTHED,141,CEGTHDM2_LT_SMOOTHED,
142,DEGT3_LT_SMOOTHED,143,DEGT2_LT_SMOOTHED,144,CWFPADJ2_SMOOTHED,
145,CADJCEQ2_SMOOTHED,146,DELN12_SMOOTHED,147,DELFN2_SMOOTHED,
148,ZVB2R2_SMOOTHED,149,ZVB2F2_SMOOTHED,150,ZVB1R2_SMOOTHED,
151,ZVB1F2_SMOOTHED,152,ZTOIL2_SMOOTHED,153,ZPOIL2_SMOOTHED,
154,GWFR3_SMOOTHED,155,GWFR2_SMOOTHED,156,GN2R3_SMOOTHED,157,GN2R2_SMOOTHED,
158,DPOIL2_SMOOTHED,159,GWFM3_SMOOTHED,160,GWFM2_SMOOTHED,161,CGPCN253_SMOOTHED,
162,CGPCN252_SMOOTHED,163,CEGTHDM2_SMOOTHED,164,DEGT3_SMOOTHED,165,DEGT2_SMOOTHED,
166,C

Followup   August 3, 2004 - 11am Central time zone:

The maximum number of components in the DECODE function, including expr, searches, results, and 
default, is 255.


 

5 stars Thanks --is there a woraround or any other way to solve my problem   August 3, 2004 - 11am Central time zone
Reviewer: Ajeet from Mumbai , India
Tom -
Thanks -- Yes I saw it in the SQL reference manual too,soorry I should have seen it before posting 
it to you.
But can you please suggest a way to write the above query where I have total 600 arguments in 
decode.

Thanks so much 


Followup   August 3, 2004 - 11am Central time zone:

looks like a union all


select * 
  from (select a, b, c, decode( r, 1, D, 2, E, ... ) column
          from t, (select rownum r from all_objects where rownum <= 100)
       )
 where column is not null
UNION ALL
select * 
  from ( select a, b, c, decode( r, 1, DD, 2, EE, ... ) column
           from t, .......



do 100 columns, then another 100, then another 100.

Or, utilize a pipelined function -- then fetches a row and calls "pipe row" n-hundred times per 
row. 

4 stars Or perhaps just...   August 3, 2004 - 12pm Central time zone
Reviewer: Padders from UK
Can you also get around this restriction by just nesting DECODEs within eachother? 

Also note that you can use in-line view to avoid repeating (and maintaining!) the DECODE in WHERE 
clause, e.g.

SELECT value
FROM  (SELECT DECODE (value, 
                1, 'ONE',
                2, 'TWO',
                3, 'THREE',
                (..snip..)
                124, 'ONE HUNDRED TWENTY-FOUR',
                125, 'ONE HUNDRED TWENTY-FIVE',
                126, 'ONE HUNDRED TWENTY-SIX',
                DECODE (value, 
                  127, 'ONE HUNDRED TWENTY-SEVEN',
                  128, 'ONE HUNDRED TWENTY-EIGHT',
                  129, 'ONE HUNDRED TWENTY-NINE',
                  (..snip..)
                  198, 'ONE HUNDRED NINETY-EIGHT',
                  199, 'ONE HUNDRED NINETY-NINE',
                  200, 'TWO HUNDRED')) new_value
FROM  (SELECT 121 value
       FROM   dual)
WHERE  new_value IS NOT NULL; 


Followup   August 3, 2004 - 12pm Central time zone:

very nice - did not think of that one. 

3 stars Help   August 19, 2004 - 8am Central time zone
Reviewer: Catherine from OH,USA
Dear Tom,
How to pivot the X values so that they appear in one single
row.

SQL> create table t(x number);

Table created.


SQL> insert into t select rownum * 10 from dictionary
  2  where rownum <= 5;

5 rows created.

SQL> commit;

Commit complete.

SQL> select * from t;

         X
----------
        10
        20
        30
        40
        50

I would like to have the result set as
 
 X 10 20 30 40 50

Could you please provide a way of doing it?

Thanks for your time.
Bye!
P.S :) Are there different ways to do it?
 


Followup   August 19, 2004 - 10am Central time zone:

you need to know the max number of columns you expect ever, then you can:

select max(decode(rownum,1,x)), 
       max(decode(rownum,2,x)),
       ....
       max(decode(rownum,N,x))
  from t;
 

3 stars Thanks   August 20, 2004 - 4am Central time zone
Reviewer: Catherine from OH,USA
Hi Tom,
Thanks for your reply.How to draft a query for a table with data as follows

SQL> create table t(x int,y varchar2(30));

SQL> insert into t
   2 select rownum,to_char(to_date(rownum,'J'),'JSP')
   3 from dictionary where rownum <= 10

SQL> update t set x = 1;

SQL> select * from t;

         X Y
---------- ------------------------------
         1 ONE
         1 TWO
         1 THREE
         1 FOUR
         1 FIVE
         1 SIX
         1 SEVEN
         1 EIGHT
         1 NINE
         1 TEN
         

11 rows selected.

SQL> select max(decode(x,1,y)) from t;

MAX(DECODE(X,1,Y))
------------------------------
TWO


I expect the result set to be 
 
SQL> select x,max(decode(x,1,y)) from t
       group by x;
 X       MAX(DECODE(X,1,Y))
----   --------------------------
 1      ONE TWO THREE FOUR FIVE ..... 


How to achieve this?



 


Followup   August 20, 2004 - 11am Central time zone:

search this site for stragg 

5 stars How do I avoid the pivot   October 11, 2004 - 8pm Central time zone
Reviewer: Venkat from Minneapolis, MN
Tom,

I have a table SEG that has about 600K rows and COMMENTS table with about 1 Million rows. And about 
250K of the SEG records have comments. Table COMMENTS includes multiple comments for each seg_id. 
My goal is to get the seg_id followed by the entire comment text. 

drop table seg ;

create table seg (seg_id number primary key, comment_id number) ;

drop table comments ;

create table comments
  (comment_id number, comment_seq_nbr number, comment_text varchar2(50) ) ;

alter table comments add constraint comments_pk primary key 
  (comment_id, comment_seq_nbr) ;

insert into comments
select object_id, 1, 'R1-'||object_name from all_objects
union all
select object_id, 2, 'R2-'||object_name from all_objects
union all
select object_id, 3, 'R3-'||object_name from all_objects
/


insert into seg
select object_id + 10000, object_id from all_objects
/


drop view seg_vw
/
create or replace view seg_vw as 
  select   s.seg_id, c.comments
         -- other <table.column> names
  from 
   (
    select comment_id,
            max(decode(rn, 1, comment_text, null) ) || 
            max(decode(rn, 2, comment_text, null) ) || 
            max(decode(rn, 3, comment_text, null) ) comments
      from
        ( select c.comment_id, c.comment_seq_nbr, 
                 c.comment_text,
                 row_number() over
                  (partition by c.comment_id 
                   order by c.comment_seq_nbr) rn
            from comments c
        )
      where rn <= 3
     group by comment_id
    ) c, seg s
    -- other <table_names> involved in the view
 where s.comment_id = c.comment_id (+)
  -- and <some more joins> for other tables
/

My queries against this view will look like this:

select * from seg_vw where seg_id = 625626;

no rows selected


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN (OUTER)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'SEG'
   3    2       INDEX (UNIQUE SCAN) OF 'SYS_C0076433' (UNIQUE)
   4    1     FILTER
   5    4       VIEW
   6    5         SORT (GROUP BY)
   7    6           VIEW
   8    7             WINDOW (SORT PUSHED RANK)
   9    8               TABLE ACCESS (FULL) OF 'COMMENTS'




Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
          5  consistent gets
          0  physical reads
         60  redo size
        131  bytes sent via SQL*Net to client
        214  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed


There are other tables (4 more) and columns that all join to the SEG table in the creation of the 
view but the join involving only these 2 tables is shown in this example.

1. Every time I query from this view, it does a full-table scan on COMMENTS table (whether there is 
a match or not). Is there a way I can rewrite the view such that the pivoting steps to fetch the 
concatenated comments are avoided unless there is a match to the COMMENT_ID in SEG table?

2. Can I alter the view definition to make it do an index scan on the COMMENT_ID column since it is 
a PK? Is it possible?

3. Is there a different/better way of concatenating the comment rows to get the entire comment 
text?

Thanks,
Venkat
 


Followup   October 12, 2004 - 7am Central time zone:

the group by and where on rn in the inline view is going to preclude view merging...

So, lets lose the outer join, use another view that we can merge into and a scalar subquery:


ops$tkyte@ORA9IR2> create or replace view comment_view
  2  as
  3  select c.comment_id, c.comment_seq_nbr, c.comment_text,
  4         row_number() over (partition by c.comment_id order by c.comment_seq_nbr) rn
  5    from comments c
  6  /
 
View created.
 
that can be merged into when we use a where clause on COMMENT_ID easily..

ops$tkyte@ORA9IR2> create or replace view seg_vw_2
  2  as
  3    select   s.seg_id,
  4             (select max( decode(rn,1,comment_text) ) || ' ' ||
  5                             max( decode(rn,2,comment_text) ) || ' ' ||
  6                             max( decode(rn,3,comment_text) )
  7                from comment_view
  8                           where comment_id = s.comment_id) comments
  9    from seg s
 10  /
 
View created.

the scalar subquery can replace the outer join and it'll just get the comment text for the one 
comment we are interested in..
 

ops$tkyte@ORA9IR2> set autotrace on
ops$tkyte@ORA9IR2> select * from seg_vw_2 where seg_id = 33430;
 
    SEG_ID COMMENTS
---------- --------------------
     33430 R1-/4c9c51bf_XSLForE
           ach R2-/4c9c51bf_XSL
           ForEach R3-/4c9c51bf
           _XSLForEach
 
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   SORT (AGGREGATE)
   2    1     VIEW OF 'COMMENT_VIEW'
   3    2       WINDOW (BUFFER)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'COMMENTS'
   5    4           INDEX (RANGE SCAN) OF 'COMMENTS_PK' (UNIQUE)
   6    0   TABLE ACCESS (BY INDEX ROWID) OF 'SEG'
   7    6     INDEX (UNIQUE SCAN) OF 'SYS_C003362' (UNIQUE)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        507  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
ops$tkyte@ORA9IR2> set autotrace off

 

5 stars Can The Following Pivot be Perfomed   October 15, 2004 - 10am Central time zone
Reviewer: A reader 
Hi Tom

Apoligies in advance if the formatting does not come out right on this one but:

Say for example I have a query that returns a result set like:

Date        Job         Pay      Bonus      Tax
------------------------------------------------
07/2004    Mgr         80000      5000     32000
07/2004    Clk         20000      200      1000
08/2004    Clk         25000      850      1300
08/2004    Cl          12000       0       500


What I would like to achieve is to perform a pivot on this result set so that the query returns the 
figures in the following way:


Job   Pay(J)  Bonus(J)  Tax(J)  Pay(A)  Bonus(A)  Tax(A)    
--------------------------------------------------------Mgr   80000   5000      32000      0        
0        0
Clk   20000   200       1000   25000      850     1300
Cl     0       0         0     12000        0      500


The number of values allowed in the pay, bonus and tax columns are not finite but the job titles 
are (300) at most.

If it is not possible to perform a pivot on my result to produce the desired output, what would you 
recommend?

Regards

Jimmy
 


Followup   October 15, 2004 - 11am Central time zone:

j? a? 

what are those?

but you can use the technique above to pivot whatever you like -- did you follow the example? does 
it make sense how the TECHNIQUE works? 

4 stars July/August pivot query   October 15, 2004 - 3pm Central time zone
Reviewer: Bob Shepard from Denver, CO USA
Based on the dates in the table under the question "Can The Following Pivot Be Performed", I'm 
assuming that "J" means "July" and "A" means "August".

One possible way of generating the desired "pivot" query is as follows:

   SELECT "Job",
      SUM("Pay(J)") As "Pay(J)",
      SUM("Bonus(J)") As "Bonus(J)",
      SUM("Tax(J)") As "Tax(J)",
      SUM("Pay(A)") As "Pay(A)",
      SUM("Bonus(A)") As "Bonus(A)",
      SUM("Tax(A)") As "Tax(A)"
   FROM (
      SELECT "Job",
         DECODE("Date",'07/2004',"Pay",0) AS "Pay(J)",
         DECODE("Date",'07/2004',"Bonus",0) AS "Bonus(J)",
         DECODE("Date",'07/2004',"Tax",0) AS "Tax(J)",
         DECODE("Date",'08/2004',"Pay",0) AS "Pay(A)",
         DECODE("Date",'08/2004',"Bonus",0) AS "Bonus(A)",
         DECODE("Date",'08/2004',"Tax",0) AS "Tax(A)"
      FROM
         (
         SELECT '07/2004' AS "Date", 'Mgr' AS "Job", 80000 AS "Pay",
            5000 AS "Bonus", 32000 AS "Tax"
         FROM dual
         UNION
         SELECT '07/2004' AS "Date", 'Clk' AS "Job", 20000 AS "Pay",
            200 AS "Bonus", 1000 AS "Tax"
         FROM dual
         UNION
         SELECT '08/2004' AS "Date", 'Clk' AS "Job", 25000 AS "Pay",
            850 AS "Bonus", 1300 AS "Tax"
         FROM dual
         UNION
         SELECT '08/2004' AS "Date", 'Cl' AS "Job", 12000 AS "Pay",
            0 AS "Bonus", 500 AS "Tax"
         FROM dual
         )
      )
   GROUP BY "Job"
   ORDER BY "Job" DESC;

Of course, in real life the innermost query would be replaced by an actual table, but a "union" 
query will suffice here.

I hope this is helpful to the reader who posed the question.  If nothing else, it was a good 
exercise for me.
 


3 stars Pivot Query !   November 9, 2004 - 6pm Central time zone
Reviewer: RK from ct, usa
I have a table

CREATE TABLE test(id varchar2(10),
    att    varchar2(10), start_date date, value varchar2(20),
    constraint test_pk primary key (id, att, start_date))
/

The data in the table would be:

INSERT INTO TEST VALUES ('A', 'SECTIND', '27-OCT-2004', 'ALL')
/
INSERT INTO TEST VALUES ('A','LB', '27-OCT-2004', 'b-.005')
/
INSERT INTO TEST VALUES ('A','UB', '27-OCT-2004', 'b+.005')
/
INSERT INTO TEST VALUES ('A','SIFLAG', '27-OCT-2004', 'S')
/
INSERT INTO TEST VALUES ('A','SIFLAG', '26-OCT-2004', 'I')
/
INSERT INTO TEST VALUES ('B', 'SECTIND', '27-OCT-2004', 'ALL')
/
INSERT INTO TEST VALUES ('B','LB', '27-OCT-2004', 'b-.005')
/
INSERT INTO TEST VALUES ('B','UB', '27-OCT-2004', 'b+.005')
/
INSERT INTO TEST VALUES ('B','SIFLAG', '27-OCT-2004', 'S')
/
COMMIT
/

SELECT * FROM TEST
/

ID             ATT            START_DATE                 VALUE                
A        SECTIND        27-OCT-04        ALL
A        LB        27-OCT-04        b-.005
A        UB        27-OCT-04        b+.005
A        SIFLAG        27-OCT-04        S
A        SIFLAG        26-OCT-04        I
B        SECTIND        27-OCT-04        ALL
B        LB        27-OCT-04        b-.005
B        UB        27-OCT-04        b+.005
B        SIFLAG        27-OCT-04        S

I would need the following output: 

When queried for  ID A:

SECTIND    LB    UB    SIFLAG
ALL    b-.005    b+.005    S
ALL    b-.005    b+.005    I

When queried for  ID B:

SECTIND    LB    UB    SIFLAG
ALL    b-.005    b+.005    S

Can you Please help me with a query with this, if it is possible with a query ? 


Followup   November 9, 2004 - 7pm Central time zone:

need more info.  I see SIFLAG can have "more than one entry" per ID

is it "0, 1, or more"

and are the others "only 0 or 1" per id



 

3 stars Pivot Query !   November 10, 2004 - 10am Central time zone
Reviewer: A reader 
SIFLAG can have "0,1,or more" values, and for all others, it would be 0 or 1 per ID.

Please let me know if have any further questions. 


Followup   November 10, 2004 - 12pm Central time zone:

ops$tkyte@ORA9IR2> variable x varchar2(5)
ops$tkyte@ORA9IR2> exec :x := 'A'
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> select       *
  2    from (select value siflag
  3            from test
  4                   where id = :x
  5                     and att = 'SIFLAG' ),
  6         (select max(decode(att,'LB',value)) lb,
  7                 max(decode(att,'UB',value)) ub,
  8                 max(decode(att,'SECTIND',value)) SECTIND
  9            from test
 10                   where id = :x )
 11  /
 
SIFLAG               LB                   UB             SECTIND
-------------------- -------------------- -------------- --------------------
I                    b-.005               b+.005         ALL
S                    b-.005               b+.005         ALL
 
ops$tkyte@ORA9IR2> exec :x := 'B'
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> /
 
SIFLAG               LB                   UB              SECTIND
-------------------- -------------------- --------------- --------------------
S                    b-.005               b+.005          ALL
 
 

4 stars Pivot Query !   November 10, 2004 - 3pm Central time zone
Reviewer: rk from ct. usa
My apologies, for missing this point. If 'SLFLAG' is not defined then the output must be

LB                   UB              SECTIND
-------------------- -------------------- ------
b-.05               b+.05          ALL



INSERT INTO TEST VALUES ('C','SECTIND','27-OCT-2004', 'ALL')
/
INSERT INTO TEST VALUES ('C','LB','27-OCT-2004', 'b-.05')
/
INSERT INTO TEST VALUES ('C','UB','27-OCT-2004', 'b+.05')
/
COMMIT
/
SELECT * FROM TEST
/

ID      ATT     START_DATE     VALUE  
----------------------------------------              
A    SECTIND    27-OCT-04    ALL
A    LB    27-OCT-04    b-.005
A    UB    27-OCT-04    b+.005
A    SIFLAG    27-OCT-04    S
A    SIFLAG    26-OCT-04    I
B    SECTIND    27-OCT-04    ALL
B    LB    27-OCT-04    b-.005
B    UB    27-OCT-04    b+.005
B    SIFLAG    27-OCT-04    S
C    SECTIND    27-OCT-04    ALL
C    LB    27-OCT-04    b-.05
C    UB    27-OCT-04    b+.05


The query works fine, but when id "C" is used, doesnot return any data back. Please help. 


Followup   November 10, 2004 - 8pm Central time zone:

ops$tkyte@ORA9IR2> delete from test where att = 'SIFLAG';
 
3 rows deleted.
 
ops$tkyte@ORA9IR2> /
 
0 rows deleted.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select siflag, lb, ub, sectind
  2    from (select 1 x, value siflag
  3            from test
  4                   where id = :x
  5                     and att = 'SIFLAG' ) a,
  6         (select 1 x,
  7                     max(decode(att,'LB',value)) lb,
  8                 max(decode(att,'UB',value)) ub,
  9                 max(decode(att,'SECTIND',value)) SECTIND
 10            from test
 11                   where id = :x ) b
 12   where b.x = a.x(+)
 13  /
 
SIFLAG   LB                   UB                   SECTIND
-------- -------------------- -------------------- --------
         b-.005               b+.005               ALL
 

5 stars Thanks   November 12, 2004 - 11am Central time zone
Reviewer: Bill Coulam from Houston, TX
This reply to response feature is great for posting questions, but lousy for being able to refer to 
an individual response.

I wanted to say thanks for your original follow-up to this thread. I'd never once in 10 years had 
to create a pivot table, but when I needed to, it was 15 seconds on your site for the answer.

Thanks! Just right. 


5 stars PIVOT ON DATES   December 3, 2004 - 12pm Central time zone
Reviewer: john steinbeck from Raleigh, NC
Hi,

Begginer here... I noticed on previous ?'s about columns on the TO_CHAR... I figured that part 
out... in the DECODE, the TO_CHAR needs to be an Alias!!!

Problem... anything I do with dates never, never, never returns all the sum values???? why, why, 
why...lol

Please look below

John




SQL> break on report
SQL> compute sum LABEL 'TOTAL' of 'JAN' on report
SQL> compute sum of 'FEB' on report
SQL> compute sum of 'MAR' on report
SQL> compute sum of 'APR' on report
SQL> compute sum of 'MAY' on report
SQL> compute sum of 'JUNE' on report
SQL> compute sum of 'JULY' on report
SQL> compute sum of 'AUG' on report
SQL> compute sum of 'SEPT' on report
SQL> compute sum of 'OCT' on report
SQL> compute sum of 'NOV' on report
SQL> compute sum of 'DEC' on report
SQL> compute sum of total on report
SQL> 
SQL> select job,
  2  max( decode(MONTH, 'JANUARY', SPENT, 0 ) ) JAN,
  3  max( decode(MONTH, 'FEBRUARY', SPENT, 0 ) ) FEB,
  4  max (decode(MONTH, 'MARCH', SPENT, 0)) MAR,
  5  max( decode(MONTH, 'APRIL', SPENT, 0 ) ) APR,
  6  max( decode(MONTH, 'MAY', SPENT, 0 ) ) MAY,
  7  max (decode(MONTH, 'JUNE', SPENT, 0)) JUNE,
  8  max( decode(MONTH, 'JULY', SPENT, 0 ) ) JULY,
  9  max( decode(MONTH, 'AUGUST', SPENT, 0 ) ) AUG,
 10  max (decode(MONTH, 'SEPTEMBER', SPENT, 0)) SEPT,
 11  max( decode(MONTH, 'OCTOBER', SPENT, 0 ) ) OCT,
 12  max( decode(MONTH, 'NOVEMMBER', SPENT, 0 ) ) NOV,
 13  max (decode(MONTH, 'DECEMBER', SPENT, 0)) DEC,
 14  max( decode(MONTH, 'JANUARY', SPENT, 0 ) ) +
 15  max( decode(MONTH, 'FEBRUARY', SPENT, 0 ) ) +
 16  max (decode(MONTH, 'MARCH', SPENT, 0)) +
 17  max( decode(MONTH, 'APRIL', SPENT, 0 ) ) +
 18  max( decode(MONTH, 'MAY', SPENT, 0 ) ) +
 19  max (decode(MONTH, 'JUNE', SPENT, 0)) +
 20  max( decode(MONTH, 'JULY', SPENT, 0 ) ) +
 21  max( decode(MONTH, 'AUGUST', SPENT, 0 ) ) +
 22  max (decode(MONTH, 'SEPTEMBER', SPENT, 0)) +
 23  max( decode(MONTH, 'OCTOBER', SPENT, 0 ) ) +
 24  max( decode(MONTH, 'NOVEMMBER', SPENT, 0 ) ) +
 25  max (decode(MONTH, 'DECEMBER', SPENT, 0)) TOTAL
 26  from (select  job, to_char(hiredate, 'MONTH') MONTH, sum(nvl(sal,0)) spent 
 27  from emp 
 28  group by job, to_char(hiredate, 'MONTH'))
 29  GROUP BY job;

JOB              JAN        FEB        MAR        APR        MAY       JUNE       JULY        AUG   
    SEPT        OCT        NOV        DEC      TOTAL
--------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
---------- ---------- ---------- ---------- ----------
ANALYST            0          0          0          0          0          0          0          0   
       0          0          0          0          0
CLERK              0          0          0          0          0          0          0          0   
       0          0          0          0          0
MANAGER            0          0          0          0          0          0          0          0   
       0          0          0          0          0
PRESIDENT          0          0          0          0          0          0          0          0   
       0          0          0          0          0
SALESMAN           0          0          0          0          0          0          0          0   
    2750          0          0          0       2750
          ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
---------- ---------- ---------- ---------- ----------
TOTAL              0          0          0          0          0          0          0          0   
    2750          0          0          0       2750

SQL> select  job, to_char(hiredate, 'MONTH') MONTH, sum(nvl(sal,0)) spent 
  2  from emp 
  3  group by job, to_char(hiredate, 'MONTH')
  4  ;

JOB       MONTH          SPENT
--------- --------- ----------
CLERK     DECEMBER        1750
CLERK     JANUARY         1300
CLERK     MAY             1100
ANALYST   APRIL           3000
ANALYST   DECEMBER        3000
MANAGER   APRIL           2975
MANAGER   JUNE            2450
MANAGER   MAY             2850
SALESMAN  FEBRUARY        2850
SALESMAN  SEPTEMBER       2750
PRESIDENT NOVEMBER        5000

11 rows selected.

SQL>  


Followup   December 3, 2004 - 1pm Central time zone:

 
ops$tkyte@ORA9IR2> select '"' || to_char( sysdate, 'MONTH' ) || '"' from dual;
 
'"'||TO_CHA
-----------
"DECEMBER "
 


see the trailing blank.... MONTH format is a fixed width field.

fmMONTH would be blank trimmed.

MM would be easier (just a number, less typos) 

5 stars PIVOT ON DATES   December 3, 2004 - 2pm Central time zone
Reviewer: john steinbeck from raleigh, NC
YOU ROCK!!!!!  You are quick to reply... thought I would wait days...

I tried the 'fmMONTH' and see what you mean compared to just 'MONTH' wow!!!

I did mm you are right much easier...

you are awesome... I hope your proud of me and my little discovery with the alias...lol

John 


Followup   December 3, 2004 - 2pm Central time zone:

I know this--a man got to do what he got to do. 

5 stars PIVOT   December 3, 2004 - 5pm Central time zone
Reviewer: John Steinbeck from Raleigh, NC
oh, is there a good book to learn more about all the extra's of SQL you show??

And is there a guy like you for FORMS on here like askmrforms.oracle.com???

Thanks again,

John 


Followup   December 4, 2004 - 10am Central time zone:

otn.oracle.com has discussion forums covering many topics.

the SQL Reference, coupled with experience and "thinking in sets" is what I used to learn.  I'm 
afraid a book of "tricks" would be so myoptic -- you'd learn 5 'tricks' and never really be able to 
take advantage of all that sql has to offer. 

4 stars pivot question   December 7, 2004 - 7am Central time zone
Reviewer: vivek saxena from uk
tom,
I have a table having price groups data (upto fifteen columns) for various code on different dates.
and data is like

 date     code  PG1   PG2  PG3  PG4 ....PG15
----------------------------------------
 01-01-04  600   10   20   30   40   
 08-01-04  600   100  200  300  400
 15-01-04  600   1000 2000 3000 4000
 01-01-04  601   500  600  700  800
 15-08-04  602   5000 6000 7000 8000
 22-01-04  603   10   20   30   40
 29-01-04  603   100  200  300  400

_______________________________________________

I want to have an output from this single table in the following form using 
PL/SQL. (oracle version 8.1.7)


          code      | CODE
-----------------------------------
  date    PG1 PG2   | PG1 PG2 PG3

i.e.

              600   |   601  |  602   | 603   
 
 01-01-04   pg1 pg2..  | pg1 pg2.. |
 08-01-04   pg1 pg2..  | pg1 pg2.. 
 15-01-04   pg1 pg2..                      pg1 pg2...
 22-01-04                                 pg1 pg2..    
290104                                     pg1 pg2 pg3..


I tried by example discussed on this site but not able to achieve it.
Tom, is it possible to get this result through PL/SQL. 
If yes, please advise, as you have always done.

Regards

Vivek 


Followup   December 7, 2004 - 10am Central time zone:

new question.... ask it when I'm taking questions. 

4 stars How to get ouput thru SQL ?   January 6, 2005 - 4am Central time zone
Reviewer: Parag Jayant Patankar from India
Hi Tom,

suppose I am having table parag having following details

SQL> desc parag
 Name                                      Null?    Type
 ----------------------------------------- -------- ------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER
 D                                                  NUMBER
 E                                                  NUMBER

I want ouput in following way by sql ( 2 values per row only ) for COBOL programs

  a, b,
  c, d,
  e

How can I do this ? ( Can I avoid to have comma [","] at last field in this case column is e ? )

regards & thanks
pjp


 


Followup   January 6, 2005 - 10am Central time zone:

i think we went through this before (not this problem, but this request)

hey, where is the simple create table, insert into.....


select decode( r, 1, a, 3, c, 5, e ), 
       decode( r, 2, b, 4, d )
  from t, ( select 1 r from dual union all select 2 from dual
            union all select 3 from dual union all select 4 from dual
            union all select 5 from dual );

 

2 stars How can I get output thru SQL ?   January 6, 2005 - 10am Central time zone
Reviewer: Parag Jayant Patankar from India
Hi Tom,

suppose I am having table parag having following details

SQL> desc parag
 Name                                      Null?    Type
 ----------------------------------------- -------- ------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER
 D                                                  NUMBER
 E                                                  NUMBER

I want ouput in following way by sql ( 2 values per row only ) for COBOL 
programs

  a, b,
  c, d,
  e

How can I do this ? ( Can I avoid to have comma [","] at last field in this case 
column is e ? )

for example I have described small table, there are few tables having more than 100 columns

regards & thanks
pjp

 


Followup   January 6, 2005 - 11am Central time zone:

you would either have to do what I did for a hundred times...

or write a program to print the data out in the fashion you wanted.

no magic. 

2 stars output for pro-cobol programs   January 6, 2005 - 11am Central time zone
Reviewer: Parag Jayant Patankar from India
Hi Tom,

thanks for your answer to my query. But for e.g I have shown parag table having only few columns 
which you can decode and get result. which you have explained to me.

I want to generat output for PRO-COBOL "copy/include" files such as cursors, fetchs and inserts for 
various oracle tables. So for this reason I want ouput from user_tab_columns having only 2 values 
per row ( otherwise in COBOL it may exceed 72 columns ). There are many tables having nearly 100 
columns. So I want to write general purpose SQL for this. For e.g if I want to generate ouput for 
pro-cobol for table "user_indexes" then  I want details as follows

INDEX_NAME, INDEX_TYPE                     
TABLE_OWNER, TABLE_NAME,
TABLE_TYPE, UNIQUENESS,
COMPRESSION, PREFIX_LENGTH,
TABLESPACE_NAME, INI_TRANS,
MAX_TRANS, INITIAL_EXTENT,
NEXT_EXTENT, MIN_EXTENTS,
MAX_EXTENTS, PCT_INCREASE,
PCT_THRESHOLD, INCLUDE_COLUMN                 
FREELISTS, FREELIST_GROUPS,
PCT_FREE, LOGGING,
BLEVEL, LEAF_BLOCKS,
DISTINCT_KEYS, AVG_LEAF_BLOCKS_PER_KEY,
AVG_DATA_BLOCKS_PER_KEY, CLUSTERING_FACTOR,
STATUS, NUM_ROWS, 
SAMPLE_SIZE,LAST_ANALYZED,
DEGREE, INSTANCES,                      
PARTITIONED, TEMPORARY                      
GENERATED, SECONDARY                      
BUFFER_POOL, USER_STATS,
DURATION, PCT_DIRECT_ACCESS              
ITYP_OWNER, ITYP_NAME                      
PARAMETERS, GLOBAL_STATS,
DOMIDX_STATUS, DOMIDX_OPSTATUS,
FUNCIDX_STATUS <= last value without comma "," attached to it !!!

As this output to be generated from user_tab_columns so in my earlier example I have not attached 
create table script. 
I have seen examples on this site for various mulitcolumn pivot tables using cartesian product with 
rownumbers but I was unable to trace question where it has been asked for single column and only 
for specified values in a row i.e here I requrie 2 values in each row maximum. 
 
I think analytical will again come for rescue but not sure.

Kindly help.

regards & thanks
pjp 


Followup   January 6, 2005 - 11am Central time zone:

you are going to write code for this OR you will use the decode example taken out to this extreme. 

2 stars One way of generating output   January 7, 2005 - 7am Central time zone
Reviewer: Parag Jayant Patankar from India
Hi Tom,

With ref to my question in this thread for having 2 values per column in AIX, following SQL 
developed

SQL> create table parag
  2  (
  3  a number, b number, c number, d number, e number, f number, g number, h number, i number, j 
number, k number,
  4  l number, m number
  5  );

Table created.


select substr(z,1,50) a3
from
(
select a.column_name c1, b.column_name c2, cnt,
case when rownum = cnt then
     decode(b.column_name, null, a.column_name, a.column_name||','||b.column_name )
     else
     a.column_name||','||b.column_name||','
     end z
from user_tab_columns a, user_tab_columns b,
                        (SELECT round(COUNT(*)/2,0) CNT
                         FROM   USER_TAB_COLUMNS
                         WHERE  TABLE_NAME = 'PARAG') C
where a.column_id + 1 = b.column_id (+)
and   a.table_name = upper('PARAG')
and   b.table_name (+) = upper('PARAG')
and   mod(a.column_id,2) = 1
order by a.column_id
)
/

A,B,
C,D,
E,F,
G,H,
I,J,
K,L,
M

regards & thanks
pjp 


5 stars Need advice on this query   January 25, 2005 - 7am Central time zone
Reviewer: Mike 
Tom,
I need your advice on how to write this query.
I have the table PA_BUDGET_LINES.
There is the composite unique key on (RESOURCE_ASSIGNMENT_ID, START_DATE). I want to find out how 
many rows for count(start_date) >240.
 


APPS@DN01> desc pa_budget_lines
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- ---------------
 RESOURCE_ASSIGNMENT_ID                                NOT NULL NUMBER(15)
 START_DATE                                            NOT NULL DATE
 LAST_UPDATE_DATE                                      NOT NULL DATE
 LAST_UPDATED_BY                                       NOT NULL NUMBER
 CREATION_DATE                                         NOT NULL DATE
 CREATED_BY                                            NOT NULL NUMBER
 LAST_UPDATE_LOGIN                                     NOT NULL NUMBER
 END_DATE                                              NOT NULL DATE
 PERIOD_NAME                                                    VARCHAR2(30)
 QUANTITY                                                       NUMBER
 RAW_COST                                                       NUMBER
 BURDENED_COST                                                  NUMBER
 REVENUE                                                        NUMBER
 CHANGE_REASON_CODE                                             VARCHAR2(30)
 DESCRIPTION                                                    VARCHAR2(255)
 ATTRIBUTE_CATEGORY                                             VARCHAR2(30)
 ATTRIBUTE1                                                     VARCHAR2(150)
 ATTRIBUTE2                                                     VARCHAR2(150)
 ATTRIBUTE3                                                     VARCHAR2(150)
 ATTRIBUTE4                             
 


Followup   January 25, 2005 - 9am Central time zone:

I  want to find out how many rows for count(start_date) >240.


it is not clear to me what you mean by that. 

4 stars Pivoting a pre-ordered hierarchy   February 11, 2005 - 5pm Central time zone
Reviewer: Jim from Vancouver
Hello Tom.

I have a stubborn hierarchy/pivot/analytic puzzle.
=============================================

-- detail table with "type" flags
create table rd
   (r_id   number,
    t_flag char(1),
    r_flag char(1),
    s_flag char(1),
    f_flag char(1),
    l_flag char(1));

insert into rd values(1, 'Y','N','N','N','N');
insert into rd values(2, 'N','Y','Y','N','N');
insert into rd values(3, 'N','N','N','Y','N');
insert into rd values(4, 'N','N','N','N','Y');
insert into rd values(5, 'N','N','N','N','Y');
insert into rd values(6, 'Y','N','N','N','N');
insert into rd values(7, 'N','Y','Y','N','N');
insert into rd values(8, 'N','N','N','Y','N');
insert into rd values(9, 'N','N','N','N','Y');
insert into rd values(10,'N','Y','Y','Y','N');
insert into rd values(11,'N','N','N','N','Y');
insert into rd values(12,'N','Y','Y','Y','N');
insert into rd values(13,'N','N','N','N','Y');
insert into rd values(14,'N','N','N','N','Y');

-- hierarchy table:
-- p_order is the order in which to process
-- rows that share a level (underneath a single parent)
create table rc
  (parent_id number,
   child_id  number,
   p_order   number);

insert into rc values(1,2,1);
insert into rc values(2,3,1);
insert into rc values(3,4,2);
insert into rc values(3,5,1);
insert into rc values(6,7,2);
insert into rc values(7,8,1);
insert into rc values(8,9,1);
insert into rc values(6,10,1);
insert into rc values(10,11,1);
insert into rc values(6,12,3);
insert into rc values(12,13,2);
insert into rc values(12,14,1);

=============================================
So what I want to get is an intermediate structure
in the proper order...

   T   R   S   F   L
   --- --- --- --- --- 
   1   
       2   2
               3
                   4
   6
       10  10  10
                   11
       7   7
               8
                   9
       12  12  12
                   14
                   13

that can be flattened out to the final structure:

   T   R   S   F   L
   --- --- --- --- --- 
   1   2   2   3   4
   6   10  10  10  11
   6   7   7   8   9
   6   12  12  12  14
   6   12  12  12  13


=============================================
Tried some things like the following embarrassing
code, but I can't get the p_order right, and also
stall when collapsing and pivoting (as shown,
tried row_number(),but flubbed that too...)

=o(
=============================================

select decode(tf,'Y',parent_id) T,
       decode(rf,'Y',child_id)  R,
       decode(sf,'Y',child_id)  S,
       decode(ff,'Y',child_id)  F,
       decode(lf,'Y',child_id)  L,
       row_number() over
         (partition by tf,rf,sf,ff
          order by o desc) rn
from
(
select rc.parent_id,
       rc.child_id,
       rd.t_flag tf,
       rd.r_flag rf,
       rd.s_flag sf,
       rd.f_flag ff,
       rd.l_flag lf,
       rc.p_order o
from
       rd,
       (select parent_id,
               child_id,
               p_order
        from   rc
        start with parent_id in
           (select r_id 
            from   rd
            where  t_flag = 'Y')
        connect by prior child_id = parent_id
        order by p_order) rc
where  rd.r_id = rc.child_id
or     rd.t_flag = 'Y'
)

Any thoughts or advice would be most apprciated.
Thanks for your consideration! 


Followup   February 12, 2005 - 12pm Central time zone:

can you verbablly explain what rd and rc are and how they relate to eachother.

problem is when looking at a query that doesn't work with data I don't understand -- it's really 
hard to figure out "what needs be done" 

5 stars RC and RD explained...   February 14, 2005 - 11am Central time zone
Reviewer: Jim from Vancouver
Hi.

RD contains (in real life) row details about a hierarchy. A given RD row can serve multiple 
purposes (hence all the "flag" columns). 

In a perfect world, the hierachy rows would follow this pattern:
*  "T" - root row
**  "R" - next level row(s)
***  "S"  - next level row(s)
****  "F"   - next level row(s)
*****  "L"    - leaf level row(s)

... but the multiplicity of flags often collapses the non-T and non-L rows into a single row that 
performs double or triple duty (strangeness up here in the pacific northwest). So that many 
hierarchies look like this:
* "T" - root row
**  "R + S + F" - next level row(s)
***   "L" - leaf level row(s)

The RC table is the hierarchy structure that shows how the RD's fit together in a "connect by 
prior" fashon.  The twist here is that there's an "order by" column which states "at this level 
within a given "T" root hierarchy, process that given level in this particular order".  For 
example: We have a single "T" root parent, with three underlying "R" childen.  So process child-1 
hierarchy first, then the child-2 hierarchy, etc.  This logic pattern repeats itself within the 
scope of each underlying hierarchical parent-child module.

So what I'm thinking (apologies - probably all wet here) is to 1) create the hierarchy, 2) collapse 
the structure, and 3) pivot around the "root" levels so that each "parent"-level data is flattened 
out down to the "leaf" level in a denormalized fashon.

Hope this helps - and thanks again for your advice.
 


Followup   February 14, 2005 - 2pm Central time zone:

what happened to row "5"

why is 5 not there but 13 and 14 are

 

5 stars Hello. Row "5" is missing because...   February 14, 2005 - 3pm Central time zone
Reviewer: Jim from Vancouver
... well, I, uh messed up, coach.  =o)
Here's where I am:

select *
from
   (select rc.*
    from rc
    start with parent_id in 
      (select r_id from rd where t_flag = 'Y') 
    connect by parent_id = prior child_id
    order by p_order)
start with parent_id in 
  (select r_id from rd where t_flag = 'Y') 
connect by parent_id = prior child_id;

PARENT_ID  CHILD_ID  P_ORDER
---------- --------- --------
1          2         1
2          3         1
3          5         1
3          4         2
6          10        1
10         11        1
6          7         2
7          8         1
8          9         1
6          12        3
12         14        1
12         13        2

... which is all properly ordered. So now I've got to figure out how to incorporate the flags into 
the query and pivot around the roots (#1 and #6).

Thanks again!
 


5 stars Use Pivot to update null records?   February 21, 2005 - 6pm Central time zone
Reviewer: Warren McCall from Victoria, Canada
Tom,

I am trying to solve a problem, and it seems that the pivot my resolve my issue, but I haven't been 
able to make it do that yet!  Here is the situation:

I have data in a table that has null values in the Site_ID field.  The reason this happened is that 
the data is loaded regularly from Excel (truncate and load) and the Site_ID values are removed in 
Excel for "viewing pleasure."  The records below each Site_ID are logically related:

ID  SITE ID REGIONAL OFFICE COMMON NAME
--- ------- --------------- -----------
1   9       Region 1        NEW YORK WHARVES LTD
2                           AE SHIPPERS        
3   10      Region 2        ESSO BULK PLANT
4                           SHELL BULK PLANT    
5           Region 2        STATION A
6                           STATION B
7                           STATION C
8                           STATION D
9                           STATION E
10                          STATION F
11  11      Region 3        RAIL RIGHT-OF-WAY
12                          STORAGE SHED
13                          FUELING DE
14                          EMERGENCY SHELTER
15  13      Region 4        CANRON

I would like to update the data so that it looks like:

ID  SITE ID REGIONAL OFFICE COMMON NAME
--- ------- --------------- -----------
1   9       Region 1        NEW YORK WHARVES LTD
2   9       Region 1        AE SHIPPERS        
3   10      Region 2        ESSO BULK PLANT
4   10      Region 2        SHELL BULK PLANT    
5   10      Region 2        STATION A
6   10      Region 2        STATION B
7   10      Region 2        STATION C
8   10      Region 2        STATION D
9   10      Region 2        STATION E
10  10      Region 2        STATION F
11  11      Region 3        RAIL RIGHT-OF-WAY
12  11      Region 3        STORAGE SHED
13  11      Region 3        FUELING DE
14  11      Region 3        EMERGENCY SHELTER
15  13      Region 4        CANRON

What do you think is the best method for this? 


Followup   February 22, 2005 - 8am Central time zone:

time for HTML/DB to erradicate excel!  keep the data in the database and let them access it via the 
web.  excel as a database, ugh.


if you were to give me a create table, insert intos -- I'd be glad to show you how merge can do 
this in 9i and even easier in 10g with a new analytic feature.

analytics and merge will be the answer here. (how big is the table, cannot be too big if it fits in 
little excel spreadsheets) 

4 stars How to write this query?   February 22, 2005 - 9am Central time zone
Reviewer: Raja from London
I want to get the second latest row from the table for grouped columns col1 and col2,where 
count(parno) > 1. Data for this query is:

create table anl (col1 varchar2(10),col2 varchar2(10),parno number);

insert into anl values('R1C1','R1C2',1);
insert into anl values('R1C1','R1C2',2);
insert into anl values('R1C1','R1C2',3);
insert into anl values('R2C1','R2C2',1);
insert into anl values('R2C1','R2C2',2);
insert into anl values('R3C1','R3C2',1);
insert into anl values('R3C1','R3C2',2);
insert into anl values('R3C1','R3C2',3);
insert into anl values('R4C1','R4C2',1);

And I want the output to be:

R1C1,R1C2,2
R2C1,R2C2,1
R3C1,R3C2,2

The row "R4C1,R4C2,1" will not be output because the count for the grouped columns is not > 1.

Please can you tell me how to go about writing this query.

Thanks
 


Followup   February 22, 2005 - 9am Central time zone:

define "latest" for us here -- I don't see a timestamp, I don't see what you would order by (i'm 
assuming parno is like a part number or something)

so, what is the "latest record" 

4 stars answer   February 22, 2005 - 9am Central time zone
Reviewer: Raja from London
Sorry, latest means ...latest by parno. I wish to see the second latest by parno. No, the table 
doesn't have any timestamp but the requirement is to get the second latest of the given parno.

I hope this is clear now.

Many thanks 


Followup   February 22, 2005 - 9am Central time zone:

I keep hoping these analytics will catch on -- they are extremely powerful and I demo them all of 
the time........

you want the second row from a group after ordering by parno from big to small.

row_number()



ops$tkyte@ORA9IR2> select *
  2    from (
  3  select col1, col2, parno,
  4         row_number() over (partition by col1, col2 order by parno desc nulls last) rn
  5    from anl
  6         )
  7   where rn = 2
  8  /
 
COL1       COL2            PARNO         RN
---------- ---------- ---------- ----------
R1C1       R1C2                2          2
R2C1       R2C2                1          2
R3C1       R3C2                2          2
 
 

5 stars Thanks   February 22, 2005 - 10am Central time zone
Reviewer: Raja from London
That's what I want! Thanks Tom! You are a STAR! 


5 stars Some more tip...   February 22, 2005 - 4pm Central time zone
Reviewer: Tamilselvan 
This is for Warren McCall, Victoria, Canada 
SQL> desc t1
 Name                             Null?    Type
 -------------------------------- -------- -----------------------
 ID                                        NUMBER(38)
 SITE_ID                                   NUMBER(38)
 REGIONAL_OFFICE                           VARCHAR2(30)
 COMM_NAME                                 VARCHAR2(30)

insert into t1 values(1 ,  9,   'Region 1', 'NEW YORK WHARVES LTD');
insert into t1 values(2,   null, null,      'AE SHIPPERS');
insert into t1 values(3,     10,'Region 2', 'ESSO BULK PLANT');
insert into t1 values(4,   null, null,      'SHELL BULK PLANT');
insert into t1 values(5,   null,'Region 2', 'STATION A');
insert into t1 values(6,   null, null,      'STATION B');
insert into t1 values(7,   null, null,      'STATION C');
insert into t1 values(8,   null, null,      'STATION D');
insert into t1 values(9,   null, null,      'STATION E');
insert into t1 values(10,  null, null,      'STATION F');
insert into t1 values(11,  11 ,  'Region 3','RAIL RIGHT-OF-WAY');
insert into t1 values(12,  null, null,      'STORAGE SHED');
insert into t1 values(13,  null, null ,     'FUELING DE');
insert into t1 values(14,  null, null,      'EMERGENCY SHELTER');
insert into t1 values(15,  13 ,  'Region 4', 'CANRON');
commit;


SQL> select * from t1 ;

        ID    SITE_ID REGIONAL_OFF COMM_NAME
---------- ---------- ------------ ------------------------------
         1          9 Region 1     NEW YORK WHARVES LTD
         2                         AE SHIPPERS
         3         10 Region 2     ESSO BULK PLANT
         4                         SHELL BULK PLANT
         5            Region 2     STATION A
         6                         STATION B
         7                         STATION C
         8                         STATION D
         9                         STATION E
        10                         STATION F
        11         11 Region 3     RAIL RIGHT-OF-WAY
        12                         STORAGE SHED
        13                         FUELING DE
        14                         EMERGENCY SHELTER
        15         13 Region 4     CANRON

15 rows selected.

SQL> get warren.sql
  1  select Y.id,
  2         (case
  3         when y.site_id is not null then y.site_id
  4         else  (select site_id
  5                  from T1
  6                 where T1.id = prev_site_id)
  7         end ) NSITE_ID,
  8         ( case
  9           when y.regional_office is not null then y.regional_office
 10           else ( select regional_office
 11                   from  T1
 12                  where  T1.id = prev_reg_id)
 13         end ) NRegional_office,
 14         y.comm_name
 15  FROM
 16  (select a.id as id , a.site_id as site_id,
 17         (select max(id)
 18           from  (select distinct id, site_id
 19                    from t1 where site_id is not null) b
 20          where  b.id < a.id ) prev_site_id,
 21         regional_office regional_office,
 22         (select max(id)
 23            from (select distinct id , regional_office
 24                    from t1 where regional_office is not null) c
 25           where c.id < a.id ) prev_reg_id
 26  from t1 a ) X,
 27  T1 Y
 28* where x.id = Y.id
SQL> /

        ID   NSITE_ID NREGIONAL_OFF COMM_NAME
---------- ---------- ------------- ------------------------------
         1          9 Region 1      NEW YORK WHARVES LTD
         2          9 Region 1      AE SHIPPERS
         3         10 Region 2      ESSO BULK PLANT
         4         10 Region 2      SHELL BULK PLANT
         5         10 Region 2      STATION A
         6         10 Region 2      STATION B
         7         10 Region 2      STATION C
         8         10 Region 2      STATION D
         9         10 Region 2      STATION E
        10         10 Region 2      STATION F
        11         11 Region 3      RAIL RIGHT-OF-WAY
        12         11 Region 3      STORAGE SHED
        13         11 Region 3      FUELING DE
        14         11 Region 3      EMERGENCY SHELTER
        15         13 Region 4      CANRON

15 rows selected.

Tom, I tried lead/lag function. I am getting previous site for the subsequent row, but for all the 
rows.

I am sure, Tom will come up with "ANALYTIC FUNCTION". 

Thanks
Tami 


3 stars How to write this query   February 23, 2005 - 12am Central time zone
Reviewer: Sujit from India
Hi, 
if a table TAB has the following entries : 
 
Column1 
------- 
Ant 
Ball 
Deck 
Eight 
Xing 
Yes 
Zen 
 
and I want to find if there is anything missing from the table TAB from the list 
('Ant', 'Ball', 'Cat', 'Deck', 'Eight', 'Fix') 
 
then the query should return : 
Cat 
Fix 

In general i want to built a table (or may be view) from a list of given values.

Thanks in advance! 


Followup   February 23, 2005 - 2am Central time zone:

then you do not want a list, you want a table.


search this site for str2tbl, then you can:

ops$tkyte@ORA9IR2> variable x varchar2(50);
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :x := 'Ant, Ball, Cat, Deck, Eight, Fix'
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from table( cast( str2tbl(:x) as str2tblType ) );
 
COLUMN_VALUE
------------------------------
Ant
Ball
Cat
Deck
Eight
Fix
 
6 rows selected.


and the rest should be easy. 

5 stars Great techniques, but how effecient   March 1, 2005 - 11am Central time zone
Reviewer: B F from Philadelphia, PA USA
Tom

It seems that this tequnique is very cool and has many applications:
1) creating a non-repeating view of the "first" record among repeating records in a repeating table 
by some key column
2) creating a view of the source code for a view (using system tables and finctions that grab a 
line of source from the long given a line number, and the rownum from all_object where rownum <= 
f_lines_in_long(dba_views_rowid)
3) creating a view with behavior like your printtable() function.  
(Views are handy for giving MS Access users their own reporting views to save too many phone calls 
to the DBA).

I have often seen the same thing done using PL_SQL functions that use cursors to select the "first" 
of something by a key column (a column that is the unique identifier on a different table), and 
only getting one of them, and returning either a data column or the rowid.

Would a view like the one below (untested) be more effecient than the function approach?

create or replace view t_nonrepeating
AS
SELECT t.*
  FROM (
         select stno                            stno, 
                max(decode(rn,1,t_rowid))       s1
           from ( select rowid        t_rowid,
                         stno,
                         course_code, 
                         row_number() over (
                            partition by stno 
                                order by course_code 
                         )                      rn
                    from t 
                ) 
       group by stno
       )u,
       t
 where t.stno = u.stno
/
 


Followup   March 1, 2005 - 12pm Central time zone:

sounds like first_value() over (partition by x order by y) or min() over (partition by x order by 
y), i don't know that I'd pivot for that. 

4 stars Inverse resultset possible?   March 1, 2005 - 3pm Central time zone
Reviewer: Dan Loomis from Raleigh, NC
Tom - I'm not sure if this falls under the pivot query category, but what I want to do is transform 
a 1 row, 10 column resultset to it's inverse - 1 column and 10 rows.

For example:

SQL> select
  2    min(id)                                         b0,
  3    percentile_disc(.1) within group (order by id)  b1,
  4     percentile_disc(.20) within group (order by id) b2,
  5     percentile_disc(.30) within group (order by id) b3,
  6     percentile_disc(.40) within group (order by id) b4,
  7     percentile_disc(.50) within group (order by id) b5,
  8     percentile_disc(.60) within group (order by id) b6,
  9     percentile_disc(.70) within group (order by id) b7,
 10     percentile_disc(.80) within group (order by id) b8,
 11     percentile_disc(.90) within group (order by id) b9,
 12     percentile_disc(1)   within group (order by id) b10
 13  from xxcts_ent_okc_k_lines_b_stg
 14  where process_flag = 'U' and operation_code = 'I';

        B0         B1         B2         B3         B4         B5         B6         B7         B8  
       B9        B10
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
---------- ----------
1279698913 1279813242 1280004250 1280110131 1280225989 1280345832 1280474377 1280581043 1280711711 
1280837723 1280978292


1 rows selected.

Switched to something like this:

         B
----------
1279698913
1279813242
1280004250
1280110131
1280225989
1280345832
1280474377
1280581043
1280711711
1280837723
1280978292

10 rows selected.

How can the original SQL statement be rewritten to accomplish this?  As you can guess this query 
was taken from your new book to generate id ranges for DIY parallelism...many thanks for that idea. 


Followup   March 1, 2005 - 3pm Central time zone:

cartesian product with a 10 row table.


here is a 3 column example:

ops$tkyte@ORA9IR2> with three
  2  as
  3  (select level r from dual connect by 1=1 and level <= 3)
  4  select decode(r,1,'ename',2,'empno',3,'sal') tag,
  5         decode(r,1,ename,2,empno,3,sal) data
  6    from scott.emp, three
  7  /
 

3 stars Re: Inverse Resultset Possible   March 1, 2005 - 5pm Central time zone
Reviewer: Dan Loomis from Raleigh, NC
At risk at being marked as inept, I don't really follow your solution.  Maybe I can give you a 
query against scott.emp to better explain my question:

Initial query:

apps@CTSDEV> select
  2    min(empno)                                         b0,
  3    percentile_disc(.25) within group (order by empno) b1,
  4    percentile_disc(.50) within group (order by empno) b2,
  5    percentile_disc(.75) within group (order by empno) b3,
  6    percentile_disc(1) within group (order by empno)   b4
  7  from scott.emp;

        B0         B1         B2         B3         B4
---------- ---------- ---------- ---------- ----------
      7499       7654       7788       7876       7934

1 row selected.

I want to pivot the result to look like the following, but obviously I want to avoid the 4 full 
index scans, which would be devastating on a large table:

apps@CTSDEV> select min(empno) b from scott.emp
  2  union all
  3  select percentile_disc(.25) within group (order by empno) b from scott.emp
  4  union all
  5  select percentile_disc(.50) within group (order by empno) b from scott.emp
  6  union all
  7  select percentile_disc(.75) within group (order by empno) b from scott.emp
  8  union all
  9  select percentile_disc(1) within group (order by empno) b from scott.emp
 10  /

         B
----------
      7499
      7654
      7788
      7876
      7934

5 rows selected.

Better yet, this would fit my needs perfectly, as I'm really trying to generate N sets of id 
ranges:

apps@CTSDEV>   select
  2      min(empno) low_id,
  3      percentile_disc(.25) within group (order by empno) high_id
  4    from scott.emp
  5    union all
  6    select
  7      percentile_disc(.25) within group (order by empno) low_id,
  8      percentile_disc(.50) within group (order by empno) high_id
  9    from scott.emp
 10    union all
 11    select
 12      percentile_disc(.50) within group (order by empno) low_id,
 13      percentile_disc(.75) within group (order by empno) high_id
 14    from scott.emp
 15    union all
 16    select
 17      percentile_disc(.75) within group (order by empno) low_id,
 18      percentile_disc(1)   within group (order by empno) high_id
 19    from scott.emp;

    LOW_ID    HIGH_ID
---------- ----------
      7499       7654
      7654       7788
      7788       7876
      7876       7934

4 rows selected.
 


Followup   March 1, 2005 - 5pm Central time zone:

what you would like is for each row to be output 5 times.

each time it is output, output the i'th column.

To get a row 5 times, we cartesian product your set with a set of 5 rows.

If these 5 rows have the numbers 1, 2, 3, 4, 5 -- we can use decode to output the i'th column:


ops$tkyte@ORA9IR2> with five
  2  as
  3  (select level r from dual connect by 1=1 and level <= 5)
  4  select * from five;
 
         R
----------
         1
         2
         3
         4
         5
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select
  2    min(empno)                                         b0,
  3    percentile_disc(.25) within group (order by empno) b1,
  4    percentile_disc(.50) within group (order by empno) b2,
  5    percentile_disc(.75) within group (order by empno) b3,
  6    percentile_disc(1) within group (order by empno)   b4
  7  from scott.emp ;
 
        B0         B1         B2         B3         B4
---------- ---------- ---------- ---------- ----------
      7369       7566       7782       7876       7934
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> with five
  2  as
  3  (
  4  select level r
  5    from dual
  6  connect by 1=1
  7     and level <= 5
  8  ),
  9  data
 10  as
 11  (
 12  select min(empno) b0,
 13         percentile_disc(.25) within group (order by empno) b1,
 14         percentile_disc(.50) within group (order by empno) b2,
 15         percentile_disc(.75) within group (order by empno) b3,
 16         percentile_disc(1) within group (order by empno)   b4
 17    from scott.emp)
 18  select decode( r, 1, b0, 2, b1, 3, b2, 4, b3, 5, b4 )
 19    from five,
 20         data
 21  /
 
DECODE(R,1,B0,2,B1,3,B2,4,B3,5,B4)
----------------------------------
                              7369
                              7566
                              7782
                              7876
                              7934
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> with five
  2  as
  3  (
  4  select level r
  5    from dual
  6  connect by 1=1
  7     and level <= 5
  8  ),
  9  data
 10  as
 11  (
 12  select min(empno) b0,
 13         percentile_disc(.25) within group (order by empno) b1,
 14         percentile_disc(.50) within group (order by empno) b2,
 15         percentile_disc(.75) within group (order by empno) b3,
 16         percentile_disc(1) within group (order by empno)   b4
 17    from scott.emp)
 18  select decode( r, 1, 'min', 2, '.25', 3, '.50', 4, '.75', 5, '1.0' ),
 19         decode( r, 1, b0, 2, b1, 3, b2, 4, b3, 5, b4 )
 20    from five,
 21         data
 22  /
 
DEC DECODE(R,1,B0,2,B1,3,B2,4,B3,5,B4)
--- ----------------------------------
min                               7369
.25                               7566
.50                               7782
.75                               7876
1.0                               7934
 


(note: if you are using 8i, just use INLINE views instead of the with) 

5 stars PERFECT   March 1, 2005 - 10pm Central time zone
Reviewer: Dan Loomis 
Thanks for spending an extra few minutes explaining your approach, which by the way is exactly what 
I was looking for.  I don't know what I would do without this website and the service you provide. 


5 stars Good - very good   March 26, 2005 - 9pm Central time zone
Reviewer: Tim B from Charlotte, NC USA
Pivot Table in Oracle very useful.  Customers want it.  Thanks for the time/trouble to produce 
this. 


2 stars column to row   April 13, 2005 - 12am Central time zone
Reviewer: Pushpendra Singh from India
Hi Tom,

I have one query: 

SELECT id, glCode, name, 
(select name from chartofaccounts where parentid = cOfAcc.id)
From chartofaccounts cOfAcc;

here this inside query 

(select name from chartofaccounts where parentid = cOfAcc.id) 

should retun all the names in a comma separated list. the names I want are those names whose 
parentId equals ID in outer table. means for ID's there may be 1, 2, ..... unknown names (or 
children) at the time of firing query, and I want all children of a parent to be in a column in 
same row in comma separated list. 

So my output will be like this:

ID       GLCODE       NAME         CHILDREN
1        GL001        P1           C1, C2, C3   
2        GL002        P1           C4, C5
3        GL003        P1           C6, C7, C8, C9
...      .....        ..           ..,..,..,...........
...
...
...      .....        ..           ..,..,..,...........

please help me.

Thanks 


Followup   April 13, 2005 - 9am Central time zone:

search this site for stragg 

4 stars   April 19, 2005 - 4pm Central time zone
Reviewer: A Reader from NY USA
Hi Tom,

I am trying to convert rows in to columns using pivot query solution. I would appreciate any help.

I have table a table test1, where results of different queries are being inserted along with the 
query number assigned to it.


When qry_no = 0,  then its a base query, with all the values of x and y.

Counts for all other values of qry_no needs to be mapped for x and y. There in total 10 queries.

create table test1
    (
    qry_no char(1)
    ,x char(2)
    ,y char(2)
    ,cnt number(2)
    );



insert into test1 values('0', 'A1','B1',null);
insert into test1 values('0', 'A2','B2',null);
insert into test1 values('0', 'A3','B3',null);
insert into test1 values('0', 'A4','B4',null);
insert into test1 values('0', 'A5','B5',null);


insert into test1 values('1', 'A1','B1',4);
insert into test1 values('1', 'A2','B2',6);
insert into test1 values('1', 'A3','B3',1);



insert into test1 values('2', 'A3','B3',8);
insert into test1 values('2', 'A4','B4',5);
insert into test1 values('2', 'A5','B5',3);


select * from test1;

Q X  Y         CNT
- -- -- ----------
0 A1 B1          
0 A2 B2          
0 A3 B3          
0 A4 B4
0 A5 B5          

1 A1 B1          4
1 A2 B2          6
1 A3 B3          1

2 A3 B3          8 
2 A4 B4          5
2 A5 B5         3 


Result

X    Y     qry1_cnt    qry2_cnt    qry10_cnt
--    --    --------    -------- .......---------
A1    B1    4
A2     B2     6
A3     B3     1        8            
A4     B4             5            
A5    B5             3             


Followup   April 19, 2005 - 7pm Central time zone:

ops$tkyte@ORA9IR2> select x, y,
  2         max(decode( qry_no, 1, cnt) ) c2,
  3         max(decode( qry_no, 2, cnt) ) c3
  4   from test1
  5   group by x,y;
 
X  Y          C2         C3
-- -- ---------- ----------
A1 B1          4
A2 B2          6
A3 B3          1          8
A4 B4                     5
A5 B5                     3
 
 

5 stars Thank you very much Tom.   April 20, 2005 - 4pm Central time zone
Reviewer: A Reader from NY, USA


5 stars Regarding old comment   May 18, 2005 - 12pm Central time zone
Reviewer: Mikito Harakiri 
<quote>
I cannot see how pivoting is could be a "subquery" of a larger query.  That just doesn't compute 
with me. 
</quote>

It could easily be an inner view. If a table is in EAV "format" you have to pivot the data in order 
to perform any  nontrivial query (eg. aggregate with group by, etc). Pure EAV table is unqueriable 
otherwise.   
 


5 stars How does Oracle Calculate it   June 26, 2005 - 2am Central time zone
Reviewer: Yousef from Kuwait
Dear Tom:
How does oracle calculate the result of this query
SQL> select job,
  2         max( decode( deptno, 10, cnt, null ) ) dept_10,
  3         max( decode( deptno, 20, cnt, null ) ) dept_20,
  4         max( decode( deptno, 30, cnt, null ) ) dept_30,
  5         max( decode( deptno, 40, cnt, null ) ) dept_40
  6    from ( select job, deptno, count(*) cnt
  7             from emp
  8            group by job, deptno )
  9   group by job
 10  /

As I understand, first the subquery is executed first, and it will return the following result
SQL>select  job, deptno, count(*) cnt
  2  from emp
  3  group by job,deptno;

JOB           DEPTNO        CNT
--------- ---------- ----------
CLERK             10          1
CLERK             30          1
MANAGER           10          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1

6 rows selected.

But then how it's calcualting the result, and why do we use max, if I change it sum i still can get 
the same result.

Best Regards, 


Followup   June 26, 2005 - 9am Central time zone:

It'll take this data:

scott@ORA9IR2> select  job, deptno, count(*) cnt
  2    from emp
  3   group by job,deptno;
 
JOB           DEPTNO        CNT
--------- ---------- ----------
CLERK             10          1
CLERK             20          2
CLERK             30          1
ANALYST           20          2
MANAGER           10          1
MANAGER           20          1
MANAGER           30          1
SALESMAN          30          4
PRESIDENT         10          1
 
9 rows selected.
 
Use decode to put it into a sparse matrix -- with the same number of rows but putting the single 
DEPTNO count into separate columns:

scott@ORA9IR2>
scott@ORA9IR2> select job,
  2         decode( deptno, 10, cnt, null ) c1,
  3         decode( deptno, 20, cnt, null ) c2,
  4         decode( deptno, 30, cnt, null ) c3,
  5         decode( deptno, 40, cnt, null ) c4
  6    from (
  7  select job, deptno, count(*) cnt
  8    from emp
  9   group by job,deptno
 10         )
 11  /
 
JOB               C1         C2         C3         C4
--------- ---------- ---------- ---------- ----------
CLERK              1
CLERK                         2
CLERK                                    1
ANALYST                       2
MANAGER            1
MANAGER                       1
MANAGER                                  1
SALESMAN                                 4
PRESIDENT          1
 
9 rows selected.

and then we use aggregation to "squish out" the white space.  Max makes the most sense since we 
just want the non-null value to appear, there is nothing to "sum" since there is only one 
observation.

Another way to have written THIS PARTICULAR query would have been:


scott@ORA9IR2> select job,
  2         sum(decode( deptno, 10, 1, null )) c1,
  3         sum(decode( deptno, 20, 1, null )) c2,
  4         sum(decode( deptno, 30, 1, null )) c3,
  5         sum(decode( deptno, 40, 1, null )) c4
  6    from emp
  7   group by job
  8  /
 
JOB               C1         C2         C3         C4
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4
 
scott@ORA9IR2>

but in general, the pivot is done via the max(decode(....)) trick 

5 stars Thanks   June 26, 2005 - 9am Central time zone
Reviewer: A reader 
Thank you alot 


4 stars Very Helpful   June 27, 2005 - 1am Central time zone
Reviewer: Hema from Georgia, USA
Hi Tom,

This is very helpful in accomplishing some of my reports.

Can you provide me the link where I can submit question directly on your site www.asktom.oracle.com 
? 

I really need your expert help in a pivot report. 

I have a table data like below ..

SELECT rmonth,calls,qa,hold FROM TRATINGS WHERE  uid='ab12345678' AND ryear =2005;

------------------------------------------
rmonth       calls       qa    hold
------------------------------------------
1            10          15    20
2            20          25    30
3            30          35    40
4            40          45    50
5            50          55    60
-------------------------------------------

I want to output like below ..
----------------------------------------
Item     Jan    Feb  Mar Apr May ..so on.. upto December
--------------------------------------- 
Calls    10     20   30  40  50 
qa       15     25   35  45  55
hold     20     30   40  50  60
---------------------------------------

Is it possible with a single SQL ? 

Thanks
Hema 


Followup   June 27, 2005 - 7am Central time zone:

when I'm taking new questions, there is a link on the home page.


this pivot is not very different from the examples above, just requires two steps.

ops$tkyte@ORA9IR2> create table t
  2  as
  3  select rownum rmonth, rownum*10 calls, 5+(rownum*10) qa, 10+rownum*10 hold
  4    from all_users
  5   where rownum <= 5;
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select decode(r,1,'Calls',2,'QA',3,'Hold') tag,
  2         max(decode(rmonth,1,data)) jan,
  3         max(decode(rmonth,2,data)) feb,
  4         max(decode(rmonth,3,data)) mar,
  5         max(decode(rmonth,4,data)) apr,
  6         max(decode(rmonth,5,data)) may
  7    from (
  8  select decode(r,1,calls,2,qa,3,hold) data, rmonth, r
  9    from t, (select level r from dual connect by level<=3)
 10         )
 11   group by decode(r,1,'Calls',2,'QA',3,'Hold')
 12  /
 
TAG          JAN        FEB        MAR        APR        MAY
----- ---------- ---------- ---------- ---------- ----------
Calls         10         20         30         40         50
Hold          20         30         40         50         60
QA            15         25         35         45         55

 

5 stars Excellent   June 28, 2005 - 8pm Central time zone
Reviewer: Hema from GA USA
Tom, Thanks for a nice solution. 


5 stars Pivot table   July 18, 2005 - 11am Central time zone
Reviewer: Ron from Germany
Tom,

please Give me the simple example for changing from rows to columns

select * from emp;

10
20
30
40
50
60
70
80
90
100
-----------------

No i want 

No
--
10,20,30,40,50,60,70,80,90,100

Thanks
Ron 


Followup   July 18, 2005 - 11am Central time zone:

search this site for stragg


that isn't columns you are showing, that is a string with commas between fields. 

4 stars Pivot   July 19, 2005 - 2am Central time zone
Reviewer: ron from Germany
Tom,

Ya, the column is varchar2 and i want the rows in columns with the comma.

like

select * from text

Name
--------
A
B
C
D

then i want to show

Name
------
A,B,C,D

please end me the query for the same.
 


Followup   July 19, 2005 - 7am Central time zone:

please read the above again???  I told you what to search for on this site. 

5 stars N number of rows with cartision   September 9, 2005 - 6am Central time zone
Reviewer: Jignesh from LONDON
I am looking for producing weekdays of two years in pivot table.  I am able to produce the same 
using following query :

select weekend_date nv_date, to_char(weekend_date,'DAY') nv_day 
from ( 
select trunc(sysdate,'YY')+rownum-1 weekend_date from all_tab_columns 
WHERE ROWNUM <= (366*2)) 
WHERE trim(to_char(weekend_date,'DAY')) in ('SATURDAY','SUNDAY') 

However, this uses all_tab_columns which I want to avoid.

I tried using with clause

At the very first step of developing query :

select level dummy from dual connect by 1=1 and level <= (365*2)

I only got 25 rows returned whereas I need two years calendar in first place.

Any better way of resolving this issue?

I want to avoid temporary tables.

Thanks 


5 stars Before actually you pick it up.... Got the solution   September 9, 2005 - 7am Central time zone
Reviewer: Jignesh from LONDON
(select trunc(sysdate,'YY')+rownum-1 weekend_date from 
(select level dummy from dual connect by 1=1 and level <= (366*2)) 
connect by 1=1 
and LEVEL = 1)


 


5 stars Months of a year   October 12, 2005 - 6am Central time zone
Reviewer: Reader from US
How can I use something like this to get the months in a year .

ie 
Jan
Feb
Mar

...
..
Dec 

Thanks  


Followup   October 12, 2005 - 7am Central time zone:

  1  with x
  2  as
  3  (select level-1 l from dual connect by level <= 12)
  4  select to_char(add_months(to_date('0101','ddmm'),l),'mon')
  5* from x
ops$tkyte@ORA10GR1> /
 
TO_
---
jan
feb
mar
apr
may
jun
jul
aug
sep
oct
nov
dec
 
12 rows selected.
 

4 stars I got it ..   October 12, 2005 - 6am Central time zone
Reviewer: Reader from US
I got it ...

(select 
decode(rownum,1,'Jan',2,'Feb',3,'Mar',4,'Apr',5,'May',6,'Jun',7,'Jul',8,'Aug',9,'Sep',10,'Oct',11,'N
ov',12,'Dec') Month from (select level dummy from dual connect by 1=1 and level <=(12)) 
connect by 1=1
and level=1 )  

BUT ,How can I use this to do an outer join with my table so that even if I do not have data for a 
particular month I can display 0.0 for that month.

I can construct some sample data if I am not clear about my question .
 


Followup   October 12, 2005 - 7am Central time zone:

ops$tkyte@ORA10GR1> with x
  2  as
  3  (select add_months(to_date('0101','ddmm'),level-1) dt
  4     from dual
  5  connect by level <= 12)
  6  select x.dt, count(all_objects.object_id)
  7    from x left outer join all_objects on ( x.dt = trunc(all_objects.created,'mm') )
  8   group by x.dt;
 
DT        COUNT(ALL_OBJECTS.OBJECT_ID)
--------- ----------------------------
01-JAN-05                          243
01-FEB-05                            0
01-MAR-05                           13
01-APR-05                           10
01-MAY-05                            7
01-JUN-05                            3
01-JUL-05                           14
01-AUG-05                           46
01-SEP-05                            0
01-OCT-05                           27
01-NOV-05                            0
01-DEC-05                            0
 
12 rows selected.
 

4 stars Complex query   December 26, 2005 - 4am Central time zone
Reviewer: Venkateswara Reddy from INDIA
Hi Tom

I have a requirement that, I need to pivot some columns data from a singe table.

for ex: my table has 3 columns and has data like this.

Col A,         Col B        Col B

301             110             Company A
301             110             Company A
301             ABC             Company A
301             ABC             Company A    
301        CDE        Company A

so, here col B data might change sometime back or it may change over the period. I want data like, 
the movement change COL B to ( here after 110 then ABC) it should appear new column like this


Col A   OLD Column A   Old Col B     New Col A     New Col B

301          110         Company A      ABC          Col A
301          110         Company A      CDE          Col A

How can I achieve this with singe SQL Query.  I am trying to generate data like above format. My 
database version is 9i. 
Appriciate your help. 


Followup   December 26, 2005 - 10am Central time zone:

no create table
no inserts 
not even going to look....


well, I did peek - I see no logical way to get from your input to the output - you would need to 
define that a tad better too. 

5 stars Query   January 18, 2006 - 4am Central time zone
Reviewer: Madhukar from India
Hi Tom,

I have a requirement as follows:
1. The table say Dept has the columns like 'Deptno','Job',... and the data looks like this

Table Dept:  
Deptno   Job
10      Manager
10      Clerk
10      Analyst
20      Attender
20      Engineer
20      Team Lead
20      Project Leader
and etc.

I want an output like as shown below.

10   Manager  Clerk    Analyst
20   Attender Engineer Team Lead  Project Leader
and etc....

Please help me in this regards.

Madhukar. 


Followup   January 18, 2006 - 8am Central time zone:

search this site for stragg for one possible approach. 

4 stars Using over (... partition ..) in a PL/SQL block   January 18, 2006 - 11am Central time zone
Reviewer: Jose from Spain
Hello Tom:
I am trying to use ...over ( ... partition .. ) in an UPDATE sentence into a PL/SQL block and i get 
an error message, but the same SQL sentence in a SQL*Plus session works correctly.
I am using oracle 8.1.7.4 over hp-ux. In the docs i have seen that the word OVER is not a reserved 
word of PL/SQL.
Does it mean that i can't use ... over ( ... partition ...) in PL/SQL?
 


Followup   January 19, 2006 - 7am Central time zone:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:3027089372477
you'll need to use dynamic sql for that construct in the really old 8i software. 

4 stars Using over (... partition ..) in a PL/SQL block   January 18, 2006 - 12pm Central time zone
Reviewer: Michel Cadot from France
Jose,

This is because direct SQL engine and PL/SQL SQL engine are not at the same level in 8i. PL/SQL 
engine is a little late and does not support some SQL syntax.
In 9i and up, SQL and PL/SQL use the same SQL engine, so what works in SQL works in PL/SQL block.

Regards
Michel
 


5 stars Thanks Michel   January 18, 2006 - 12pm Central time zone
Reviewer: Jose from Spain
Thanks Michel for your clarification.
 


5 stars Convert column having NULL value to rows   February 2, 2006 - 6am Central time zone
Reviewer: Narendra from India
Hi Tom,

Following is my table definition
CREATE TABLE LND_CHT
(
  ID  VARCHAR2(14 BYTE)  NOT NULL,
  P_NAME    VARCHAR2(30 BYTE),
  P_OWNER           VARCHAR2(30 BYTE)
)

Here is data:
INSERT INTO LND_CHT ( INTERCHANGE_ID, PROJECT_NAME, OWNER ) VALUES ( 
'A1', 'P1', 'O1'); 
INSERT INTO LND_CHT ( INTERCHANGE_ID, PROJECT_NAME, OWNER ) VALUES ( 
'A2', 'P2', NULL); 
INSERT INTO LND_CHT ( INTERCHANGE_ID, PROJECT_NAME, OWNER ) VALUES ( 
'A3', NULL, 'O3'); 
INSERT INTO LND_CHT ( INTERCHANGE_ID, PROJECT_NAME, OWNER ) VALUES ( 
'A4', NULL, NULL); 
COMMIT;

I need following output:
A3    Project Name
A4    Project Name
A2    Owner
A4    Owner

I have used following query:
SELECT interchange_id, Nvl(project_name, 'Project Name') 
FROM LND_CHT 
WHERE project_name IS NULL 
UNION ALL 
SELECT interchange_id, Nvl(owner, 'Owner') 
FROM LND_CHT 
WHERE owner IS NULL

However, it requires 2 full table scans. Is there any better way to achieve the same?

Thanks 


Followup   February 2, 2006 - 12pm Central time zone:

that query does not give that output.

Not that I could really run the example - since the column names don't match up.


Not sure why you want
A4 Owner

and not 
A4 Project Name 

4 stars using pipelining for pivoting   February 2, 2006 - 12pm Central time zone
Reviewer: A reader from East Coast
You suggested using pipelining above. When testing, I found the foll. for pipelined functions.
Can you provide some insight???

Why are consistent gets and recursive calls more when using the pipelined function

I run 2 sql's , one is a select * from emp 
and the other as select * from table(test_ppln.f1()) (the pipelined fn code is below).
Please see the results . Why are there more consistent and recursive calls gets when using 
pipelining?
I understand that there is overhead in piping the row but why does that impact the consistent 
gets??


CREATE package test_ppln
as
 type tab1 is table of emp%rowtype ;
 function f1 return tab1 pipelined;
end ;
/
CREATE package body test_ppln
as
 function f1 return tab1 pipelined
 as
  l_rec emp%rowtype ;
  cursor c1
  is
  select * from emp;
 begin
  open c1 ;
  loop
   fetch c1 into l_rec ;
   exit when c1%notfound ;
   pipe row (l_rec) ;
  end loop ;
  close c1 ;
 return ;
 end ;
end ;
/

SQL> select * from emp;

14 rows selected.


Statistics
----------------------------------------------------------
        490  recursive calls
          0  db block gets
         96  consistent gets
         23  physical reads
          0  redo size
       1346  bytes sent via SQL*Net to client
        500  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
         14  rows processed

SQL>  select * from table(test_ppln_1.f1());

14 rows selected.


Statistics
----------------------------------------------------------
       8575  recursive calls
          0  db block gets
       2038  consistent gets
        223  physical reads
          0  redo size
       1340  bytes sent via SQL*Net to client
        500  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
        134  sorts (memory)
          0  sorts (disk)
         14  rows processed 


Followup   February 2, 2006 - 2pm Central time zone:

couple of things....

run things "twice" - your select * from emp for example - it should have zero recursive calls, 
maybe 4 or 5 IOs.  Not 

Statistics
----------------------------------------------------------
        490  recursive calls
          0  db block gets
         96  consistent gets
         23  physical reads

hard parsing, cold system - lots of "extra one time stuff"


another thing - sqlplus array fetches 15 rows at a time - select * from emp - did maybe 1 or 2 
fetches (sqlplus fetches 1 row and then fetches the rest 15 at a time).  In the plsql routine, you 
fetched a single row at a time - sure way to increase IO.

Just like this:

ops$tkyte@ORA9IR2> set autotrace traceonly statistics;
ops$tkyte@ORA9IR2> set arraysize 2
ops$tkyte@ORA9IR2> select * from emp;

14 rows selected.


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
       2060  bytes sent via SQL*Net to client
        569  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

ops$tkyte@ORA9IR2> set arraysize 15
ops$tkyte@ORA9IR2> select * from emp;

14 rows selected.


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
       1307  bytes sent via SQL*Net to client
        503  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         14  rows processed

 

3 stars Correction   February 2, 2006 - 11pm Central time zone
Reviewer: Narendra from India
Hi Tom,

Sorry for my incorrect input.
Here are corrected details:
CREATE TABLE LND_CHT
(
  ID  VARCHAR2(14 BYTE)  NOT NULL,
  P_NAME    VARCHAR2(30 BYTE),
  P_OWNER           VARCHAR2(30 BYTE)
)

INSERT INTO LND_CHT ( ID, P_NAME, P_OWNER ) VALUES ( 
'A1', 'P1', 'O1'); 
INSERT INTO LND_CHT ( ID, P_NAME, P_OWNER ) VALUES ( 
'A2', 'P2', NULL); 
INSERT INTO LND_CHT ( ID, P_NAME, P_OWNER ) VALUES ( 
'A3', NULL, 'O3'); 
INSERT INTO LND_CHT ( ID, P_NAME, P_OWNER ) VALUES ( 
'A4', NULL, NULL); 
COMMIT;

I need following output:
A3    Project Name       => Because row with ID "A3" has P_NAME with NULL value
A4    Project Name       => Because row with ID "A4" has P_NAME with NULL value
A2    Owner              => Because row with ID "A2" has P_OWNER with NULL value
A4    Owner              => Because row with ID "A4" has P_OWNER with NULL value

I have used following query:
SELECT id, 'Project Name') 
FROM LND_CHT 
WHERE p_name IS NULL 
UNION ALL 
SELECT id, 'Owner'
FROM LND_CHT 
WHERE p_owner IS NULL

Would appreciate your help. 


Followup   February 3, 2006 - 2pm Central time zone:

you would need to cartesian join this table with a table that has as many rows as columns you want 
to check.

ops$tkyte@ORA10GR2> with data as (select level r from dual connect by level <= 2)
  2  select id,
  3         case when r=1 and p_owner is null then 'owner'
  4                  when r=2 and p_name is null then 'project'
  5                  end tag
  6    from lnd_cht, data
  7  where (r=1 and p_owner is null)
  8     or (r=2 and p_name is null);

ID             TAG
-------------- -------
A2             owner
A4             owner
A3             project
A4             project

 

3 stars Correction   February 2, 2006 - 11pm Central time zone
Reviewer: Narendra from India
Tom,

Sorry for one more mistake.
The SELECT that generates results will be:
SELECT id, 'Project Name'
FROM LND_CHT 
WHERE p_name IS NULL 
UNION ALL 
SELECT id, 'Owner'
FROM LND_CHT 
WHERE p_owner IS NULL

Regards 


4 stars Transpose   February 9, 2006 - 10am Central time zone
Reviewer: Ratan from IND
Hi Tom,
I need your help in formulating my query to transpose the records.
Query:
SELECT     A.CAT_NBR,
    C.ITEM_NOTES 
FROM     TB_007CAT_MSTR A,
    TB_084INVNT_STATUS B,
    TB_OST06459_FCT_ITEM_NOTES C 
WHERE     A.CAT_NBR= B.CAT_NBR 
AND     A.CAT_NBR = C.CAT_NUM(+) 
AND     A.CAT_NBR IN (SELECT DISTINCT B.CAT_NBR FROM TB_001INVCE_SUMRY A, TB_002INVCE_DET B WHERE 
A.INVCE_NBR = B.INVCE_NBR 
AND A.INVCE_DATE = B.INVCE_DATE 
AND A.INVCE_WRHSE = B.INVCE_WRHSE 
AND A.CHG_TO_CUST = B.CHG_TO_CUST 
AND  B.PROCESS_DATE >= '20030101' 
AND A.DIST_APRCH = 'X')

Output:
CAT#            INOTES
--------------------------------------------------------- 
DC300 KIT    KIT IS TO BE SENT OUT USING "EXCHAN
DC300 KIT    GE" FUNCTION FOR ENGINEERS USE ONLY
DC300 KIT    !!!
DC300 KIT    SEE NELDA OR CHARLIE - WEIGHT 46.0
DC300 KIT    LBS
DC300 KIT    02/01/01

I need to modify the query to transpose the data such that for 1 Cat# should display 1 record by 
concatinating all INOTES values.
Ex:
CAT#            INOTES
--------------------------------------------------------- 
DC300 KIT       KIT IS TO BE SENT OUT USING "EXCHANGE" FUNCTION FOR ENGINEERS USE ONLY !!! SEE 
NELDA OR CHARLIE - WEIGHT 46.0

Please help me to formulate query. Will it be possible in single SQL statement?

Thanks in advance.

Ratan
 


Followup   February 10, 2006 - 11am Central time zone:

I don't see any way to order this data, there is no order by?  what sorts it - else data comes out 
any old way it feels like it wants to.

do you have a maximum number of "lines" of inotes?  
will the concatenation of inotes exceed 4000 bytes? 

3 stars To Ratan   February 9, 2006 - 2pm Central time zone
Reviewer: Michel Cadot from France
Look at the answer above:
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:766825833740#22484540637096
 


4 stars Related HTML crosstab   April 5, 2006 - 9am Central time zone
Reviewer: ottar soerland from uk
I find the following little utility useful sometimes to create a HTML crosstab report from a query:


create type pivot_output_t as table of varchar2(1000);
/

CREATE OR REPLACE FUNCTION pivot_output(p_col1_heading IN VARCHAR2, 
                                        p_col2_heading IN VARCHAR2,
                                        p_col1 IN VARCHAR2,
                                        p_col2 IN VARCHAR2,
                                        p_col3 IN VARCHAR2,
                                        p_from IN VARCHAR2) 
                   RETURN pivot_output_t pipelined IS
TYPE t_curRef IS REF CURSOR;
l_curRef t_curRef;
col1 varchar2(30);
col2 varchar2(30);
col3 varchar2(30);
l_from varchar2(1000);
l_cur_txt VARCHAR2(32000);
l_line_cnt NUMBER:=0;
l_col1_vals NUMBER;
l_col2_vals NUMBER;
l_desc VARCHAR2(300);
TYPE t_num IS TABLE OF NUMBER INDEX BY VARCHAR2(100);
TYPE t_num2 IS TABLE OF t_num INDEX BY VARCHAR2(100);
TYPE t_char IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
l_x_head t_char;
l_y_head t_char;
l_x_ind NUMBER:=0;
l_y_ind NUMBER:=0;
l_tab t_num2;
l_x t_num;
l_y t_num;      
l_colx_head VARCHAR2(40);
l_coly_head VARCHAR2(40);
l_g_tot NUMBER:=0;
BEGIN


pipe row('</tr>');    
pipe row('</table> <br>');      
pipe row('<br>'); 
pipe row('<br>'); 
EXECUTE IMMEDIATE 'select COUNT(DISTINCT '||p_col1||') '||p_from 
INTO l_col1_vals;
EXECUTE IMMEDIATE 'select COUNT(DISTINCT '||p_col2||') '||p_from 
INTO l_col2_vals;

IF l_col1_vals < l_col2_vals THEN
   l_colx_head:=p_col2_heading;
   l_coly_head:=p_col1_heading;
   l_cur_txt:='SELECT '||p_col2||' source_x,
                      '||p_col1||' source_y,
                      '||p_col3||' prn_col3_val '||p_from;
ELSE
   l_colx_head:=p_col1_heading;
   l_coly_head:=p_col2_heading;          
   l_cur_txt:='SELECT '||p_col1||' source_x,
                      '||p_col2||' source_y,
                      '||p_col3||' prn_col3_val '||p_from;
END IF;

OPEN l_curRef FOR l_cur_txt;
LOOP
   FETCH l_curRef INTO col1, col2, col3;
   EXIT WHEN l_curRef%NOTFOUND;
      l_tab (col1) (col2) := col3;
      IF l_x.EXISTS(col1) THEN
         l_x(col1):= l_x(col1) + col3;
      ELSE
         l_x(col1):= col3;
         l_x_ind:=l_x_ind+1;
         l_x_head(l_x_ind):=col1;
      END IF;         
      IF l_y.EXISTS(col2) THEN
         l_y(col2):= l_y(col2) + col3;
      ELSE
         l_y(col2):= col3;
         l_y_ind:=l_y_ind+1;
         l_y_head(l_y_ind):=col2;         
      END IF;                              
END LOOP;    
CLOSE l_curRef;  
pipe row('<table border=1 cellpadding="3" cellspacing="0" bgcolor=#FFFFFF> <tr>');   
pipe row('<td align=center  style=''background:99CCCC;font-size:10.0pt''> '||
          l_coly_head||'</td>');   

FOR c IN l_y_head.FIRST..l_y_head.LAST LOOP
   pipe row('<td rowspan=2 align=center  style=''background:99CCCC;font-size:10.0pt''> '||
            l_y_head(c)||'</td>');
END LOOP;
 pipe row('<td rowspan=2 align=center  style=''background:CCCCCC;font-size:10.0pt''>  SUM </td>');
pipe row('<tr><td align=center  style=''background:99CCCC;font-size:10.0pt''> '||
         l_colx_head||'</td>');                  
FOR x IN l_x_head.FIRST..l_x_head.LAST LOOP
   pipe row('</tr><tr>');
   pipe row('<td align=right  style=''background:99CCCC;font-size:10.0pt''>'||
            l_x_head(x)||'</td>');
   FOR y IN l_y_head.FIRST..l_y_head.LAST LOOP
      IF l_tab(l_x_head(x)).EXISTS(l_y_head(y)) THEN
         pipe row('<td align=right  style=''font-size:10.0pt''>'||
                  to_char(l_tab(l_x_head(x)) (l_y_head(y)),'999,999,999,999')||'</td>' );
      ELSE
         pipe row('<td align=right  style=''font-size:10.0pt''>0</td>' );
      END IF;
   END LOOP;
   pipe row('<td  align=right style=''background:CCCCCC;font-size:10.0pt''>'||
            to_char(l_x(l_x_head(x)),'999,999,999,999')||'</td>');
END LOOP;
pipe row('</tr><tr><td align=center  style=''background:CCCCCC;font-size:10.0pt''> SUM</td>');
FOR y IN  l_y_head.FIRST..l_y_head.LAST LOOP
   pipe row('<td align=right  style=''background:CCCCCC;font-size:10.0pt''> '||
            to_char(l_y(l_y_head(y)),'999,999,999,999')||'</td>');
   l_g_tot:=l_g_tot+l_y(l_y_head(y));
END LOOP;            
pipe row('<td align=right  style=''background:CCCCCC;font-size:10.0pt''> '||
         to_char(l_g_tot,'999,999,999,999')||'</td>');

END;
/


CREATE TABLE CFL (season varchar2(100),team varchar2(100),points number);

INSERT INTO CFL (season, team, points) VALUES (2005, 'Argonauts', 22);
INSERT INTO CFL (season, team, points) VALUES (2005, 'Alouettes', 20);
INSERT INTO CFL (season, team, points) VALUES (2005, 'Renegades', 14);
INSERT INTO CFL (season, team, points) VALUES (2005, 'Tiger-Cats', 10);
INSERT INTO CFL (season, team, points) VALUES (2005, 'Team 1', 12);
INSERT INTO CFL (season, team, points) VALUES (2005, 'Team 2', 12);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Argonauts', 22);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Alouettes', 20);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Renegades', 14);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Tiger-Cats', 10);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Team 1', 12);
INSERT INTO CFL (season, team, points) VALUES (2004, 'Team 2', 12);
INSERT INTO CFL (season, team, points) VALUES (2003, 'Argonauts', 22);
INSERT INTO CFL (season, team, points) VALUES (2003, 'Alouettes', 20);
INSERT INTO CFL (season, team, points) VALUES (2003, 'Renegades', 14);
INSERT INTO CFL (season, team, points) VALUES (2003, 'Tiger-Cats', 10);
INSERT INTO CFL (season, team, points) VALUES (2003, 'Team 2', 12);

SET NEWPAGE 0
SET SPACE 0
SET LINESIZE 200
SET PAGESIZE 0

select * from 
table(pivot_output('SEASONS','TEAMS','season','team','points','from cfl'));
 


3 stars Pivot query   April 11, 2006 - 12pm Central time zone
Reviewer: Reader from Denver, CO
Hi,

The cross tab query was very useful, but what if I have to display 3 values under one group and one 
column?? for eg:

customer   mth1             mth2       mth3  ......
cust1      1000 (Rent1)    2000(rent1) ...
           1200(Rent2)     1000(rent2) ...
           1300(Rent3)     1200(rent3) ...
cust2      5000(rent1)     4000(rent1) ...
           3000(rent2)     2000(rent2)  ...
           1400(rent3)     3500(rent3)  ....

... and so on

the table contains data like this
customer | mths    | rent1 |rent2 | rent3 ...
cust1      04/2006   1000   1200    1300
cust1     05/2006    5000   3000    1400
cust2     04/2006    2000    1000   1200 ..
cust2     05/2006    4000    2000   3500

the 'mths' column is fixed.. please respond as soon as you can..

Thanks, 


Followup   April 11, 2006 - 6pm Central time zone:

so you need to turn each row into N rows (where N is the number of month columns - gotta wonder 
what the person modeling this data was thinking...)

no create table
no insert intos
no look.

 

4 stars reverse pivoting a data result   April 27, 2006 - 5pm Central time zone
Reviewer: A reader from MD USA
Tom:

I got a file ( or Oracle table ) like a spreadsheet, not I need to construct it back to relational 
structure, an example would like this:
I have a report
Name  Day1 Day2 Day3 Day4 Day5
Ad    10   9     10   8   9
Tom    8   8      7   6   7  
Jack   5   6      6   7   8
Now I need to show like:
Name  Day   Hours
Ad    day1  10
Ad    day2  9
.

Please advice
Thanks 


Followup   April 28, 2006 - 1am Central time zone:

with data
as
(select level l from dual connect by level <= 5)
select name, 
       'day' || l day,
       decode(l,1,day1,2,day2,3,day3,4,day4,5,day5) hours
  from T, data;

 

4 stars reverse pivoting a data result   April 27, 2006 - 5pm Central time zone
Reviewer: A reader from MD USA
I came up a with solution, maybe there is a better to do it,
select name, 'day1' as day, day1 as hours from thistable
union
select name, 'day2' as day, day2 as hours from thistable
union
.
.

pretty simple, sorry for asking dumb questions. 


Followup   April 28, 2006 - 1am Central time zone:

you would never want to use union for that, "union all" maybe - but not union.

But see above for an alternative. 

4 stars reverse pivoting a data result   April 28, 2006 - 10am Central time zone
Reviewer: A reader from MD USA
Thanks a lot 


3 stars   May 4, 2006 - 6am Central time zone
Reviewer: Marcel from Cologne Germany
Hi Tom,

I hvae a problem with creating a pivot query.
Below is a case like my, with dummy data.

I don't know how much courses are booked , when I run the query, so the columns displayed is not 
always the same.

he're I have no idea how I could calculate the number of rows and then replacing them with the 
heading cols.

Can you please give me a tip how I could resolve this.

Regards
Marcel

create table training ( user_id_pk int, course_user varchar2(25) );

create table traing_query (id_pk int ,user_id_fk int ,id_fk int , planed int);

create table traing_course (id_pk int, course_desc varchar2(50) );


insert into training values (1,'User_1');
insert into training values (2,'User_2');
insert into training values (3,'User_3');
insert into training values (4,'User_4');


insert into traing_query values (1,1,1,0);
insert into traing_query values (2,1,2,0);
insert into traing_query values (3,1,3,0);
insert into traing_query values (4,1,4,0);
insert into traing_query values (5,2,1,0);
insert into traing_query values (6,2,2,0);
insert into traing_query values (7,3,3,0);
insert into traing_query values (8,3,4,0);
insert into traing_query values (9,4,1,0);
insert into traing_query values (10,4,2,0);


insert into traing_course values (1,'SQL');
insert into traing_course values (2,'Enterprise Manager');
insert into traing_course values (3,'SQL Pivot');
insert into traing_course values (4,'SQL Functions');
insert into traing_course values (5,'dbms_sql');
insert into traing_course values (6,'APEX');
insert into traing_course values (7,'DBMS_STAT');
insert into traing_course values (8,'SQLPLUS');


    SQL  Enterprise Manager  SQL Pivot  Learn SQL Functions  dbms_sql  APEX  DBMS_STATS  SQLPLUS
    
user_1     0     0                 0                0
user_2     0     0                 0
user_3                                 0                0
user_4     0                   0                 

 


Followup   May 4, 2006 - 7am Central time zone:

take a query that selects username, whatever-count-you-need, course_desc (I assume you can get that 
one)


Then, you will query the traing_course table to figure out what columns you'll have and use the 
output of the query against traing_course to dynamically constraint a query in the form:


select username, 
       max( decode(course_desc,'Enterprise Manager',cnt) ) "Enterprise Manager",
       ...
       max( decode(course_desc,'APEX',cnt) ) "APEX"
  from (your_first_query)
 group by username;


but basically you would have to 

a) query traing_course to discover the columns
b) dynamically constraint the above query based on that.
 

5 stars Pivot Query   May 17, 2006 - 8pm Central time zone
Reviewer: PRS from USA
Tom,
   I have table X which has two columns.
   Data is following
   Id   Desc
   1    My Test
   1    My Test1
   1    My Test2
   2    My Test
   2    My Test

   I need output as shown below a Pivot.
   Id   Desc
   1    My Test - My Test1 - My Test2
   2    My Test - My Test

   Number of rows can be dynamic for a given id.
   I want to achive through SQL without writing an ORACLE function. Is there any way we can achive 
this?
   We are using oracle 9.2.0.6 - 64 bit on Solaris-8.

Thanks in advance,
PRS 


Followup   May 18, 2006 - 10am Central time zone:

search for stragg - people have demonstrated how you can do this with connect by and 
sys_connect_by_path()

 

3 stars What is the reason ?   May 18, 2006 - 10am Central time zone
Reviewer: A reader 
what is the technical reason behing 

"Sort key too long" Error ?

I know why it happens I don't know why oracle has to do it in one block ? 


Followup   May 19, 2006 - 9am Central time zone:

implementation restriction.

I'm sure your code has some here and there as well. 

4 stars   June 12, 2006 - 7pm Central time zone
Reviewer: A reader 


5 stars A simple way to create Excel like Pivot Table   June 20, 2006 - 12am Central time zone
Reviewer: Ahmed Saber from Sydney, NSW, Australia
select 1 as gr, 'EmpID/->State ' as Row_Col_Head, 
max(decode (ColNo, 1, state, 0)) C1,
max(decode (ColNo, 2, state, 0)) C2,
max(decode (ColNo, 3, state, 0)) C3,
max(decode (ColNo, 4, state, 0)) C4,
max(decode (ColNo, 5, state, 0)) C5
from
(
select state, count(*) n, row_number()  
over (partition by 1 order by 1) as ColNo
from sales group by state
)
union all
select 2 as gr, empid, 
to_char(max(decode (ColNo, 1, n, 0))),
to_char(max(decode (ColNo, 2, n, 0))),
to_char(max(decode (ColNo, 3, n, 0))),
to_char(max(decode (ColNo, 4, n, 0))),
to_char(max(decode (ColNo, 5, n, 0)))
from
(
select a.empid, a.state,  b.ColNo, a.n from  
(select empid, state, count(*) n
from sales 
group by empid, state) a,
(
select state, count(*) c1, row_number()  
over (partition by 1 order by 1) as ColNo
from sales group by state
) b
where a.state = b.state
) group by empid
union all
select 3 as gr, 'Total ', 
to_char(max(decode (ColNo, 1, n, 0))),
to_char(max(decode (ColNo, 2, n, 0))),
to_char(max(decode (ColNo, 3, n, 0))),
to_char(max(decode (ColNo, 4, n, 0))),
to_char(max(decode (ColNo, 5, n, 0)))
from
(
select state, count(*) n, row_number()  
over (partition by 1 order by 1) as ColNo
from sales group by state
) 


5 stars A Simple way to create Excel like Pivot Table   June 20, 2006 - 1am Central time zone
Reviewer: Ahmed Saber from Sydney, NSW, Australia
Two questions for Tom :

1. How to eliminate the repetition (please see the code below)?
2. How to replace C1, C2,... (column names) by actual "State" code?

Thanks for maintaining this wonderful site!!!!!

to_char(max(decode (ColNo, 1, n, 0))),
to_char(max(decode (ColNo, 2, n, 0))),
to_char(max(decode (ColNo, 3, n, 0))),
to_char(max(decode (ColNo, 4, n, 0))),
to_char(max(decode (ColNo, 5, n, 0))) 


Followup   June 20, 2006 - 9am Central time zone:

you give no context whatsoever.


 

5 stars Pivoting my Query   June 22, 2006 - 4am Central time zone
Reviewer: suren 
Tom,

I have two tables

Tab1

Lookup Code
Lookup Type
Description

Tab 2

Bureau code
member code
partner code
x
y
z


My requirment is a bit typical (i guess)

Tab1
---------------------------------------------
lookupcode     lookup type       desc
----------------------------------------------
TRA            Bureau code        Sample Desc1
CCHK           Membder Code       Sample Desc2
CMSI           Partner Code       Sample Desc3

TAB2
---------------------------------------------------------------
Bureau Code     Member Code     Partner Code   x     y    z
----------------------------------------------------------------
TRA                CCHK            CMSI         1     2   3

i want the data to be fetched in this fashion
---------------------------------------------------------------
x    y     z        DESC1            DESC2            DESC3
---------------------------------------------------------------
1     2    3       Sample Desc1    Sample Desc2     Sample Desc3



How can i get the description of respective Lookup codes from tab1 
while querying tab2.

I fell pivoting the query can resolve this, but unable to frame the query 

I have refered to lot examples but to be frank some how i dont get the concept of Pivoting the 
Query can you explain for all of us to learn and master this concept .... would be great if 
explaination can be with above example.


Thanks on behalf of all who may read this.. i know its going to help each one of us out there.
 

 


Followup   June 22, 2006 - 12pm Central time zone:

hmm.  deja-vu.

go to other place you put this same exact text. 

5 stars Awesome   June 28, 2006 - 4am Central time zone
Reviewer: Paul from Sydney Australia
I finally got it. I have been able to do the rows to columns for a while but not the columns to 
rows. This will go a long way to solve the queries i see which are like this.

select a,b,c,count(*)
from t
group by a,b,c
union
select d,e,c,count(*)
from t
group by d,e,c 


4 stars generic query for transpose   July 3, 2006 - 9pm Central time zone
Reviewer: RRM from India
Hi Tom,

Can we transform a table like following 

A1   B1   C1  D1
A2   B2   C2  D2
A3   B3   C3  D3

into 

A1   A2   A3
B1   B2   B3
C1   C2   C3
D1   D2   D3

So, the no. of rows in first table simply becomes no. of cols in second table.

I mean just a matrix transpose of any table? I wanted to do it using cursor to build dynamic query, 
but couldn't do it.
 


Followup   July 7, 2006 - 8pm Central time zone:

well, if you KNOW the number of columns....
ops$tkyte@ORA10GR2> select * from t;

C1 C2 C3
-- -- --
A1 A2 A3
B1 B2 B3
C1 C2 C3
D1 D2 D3

ops$tkyte@ORA10GR2> with data
  2  as
  3  (select rownum rnum, t.*
  4     from (select * from t order by c1) t
  5  ),
  6  rows_to_have
  7  as
  8  (select level rr
  9     from dual
 10  connect by level <= 3 -- number of columns in T
 11  )
 12  select rnum,
 13         max( decode( rr, 1, c1 ) ),
 14         max( decode( rr, 2, c2 ) ),
 15         max( decode( rr, 3, c3 ) )
 16    from data, rows_to_have
 17   group by rnum
 18  /

      RNUM MA MA MA
---------- -- -- --
         1 A1 A2 A3
         2 B1 B2 B3
         4 D1 D2 D3
         3 C1 C2 C3

 

5 stars Dynamically write date as column name in pivot query   July 4, 2006 - 10am Central time zone
Reviewer: Kubilay from London , UK
Hi Tom

I have a question today:

I have two tables as follows:

CREATE TABLE "TA" 
   (    "A_ID" NUMBER, 
    "A_TXT" CHAR(10 BYTE)
   ) ;

Insert into "TA" ("A_ID","A_TXT") values (1,'Tazan     ');
Insert into "TA" ("A_ID","A_TXT") values (2,'Hercules  ');
Insert into "TA" ("A_ID","A_TXT") values (3,'Ulysses   ');
Insert into "TA" ("A_ID","A_TXT") values (4,'Spiderman ');

  CREATE TABLE "TB" 
   (    "B_ID" NUMBER, 
    "A_ID" NUMBER, 
    "B_DAT" DATE
   ) ;

Insert into "TB" ("B_ID","A_ID","B_DAT") values (30,3,to_date('04-JUL-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (10,1,to_date('04-JUL-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (20,2,to_date('04-MAY-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (50,3,to_date('18-MAY-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (60,3,to_date('21-JUL-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (70,2,to_date('04-MAY-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (80,2,to_date('04-MAY-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (85,1,to_date('11-JUL-06','DD-MON-RR'));
Insert into "TB" ("B_ID","A_ID","B_DAT") values (40,3,to_date('27-JUN-06','DD-MON-RR'));


I want to do an analytical query on them as follows:

SELECT person,
MAX(decode(cdate,   to_char(add_months(last_day(sysdate),   -1),   'mm-yy'),   cnt,   0)) jun06,
MAX(decode(cdate,   to_char(add_months(last_day(sysdate),   -2),   'mm-yy'),   cnt,   0)) may06
from
  (SELECT ta.a_txt person,      to_char(tb.b_dat, 'mm-yy')   cdate,    count(*) cnt
   FROM ta,    tb
          WHERE 
          ta.a_id=tb.a_id
           GROUP BY ta.a_txt, tb.b_dat)
  GROUP BY person

PERSON     JUN06                  MAY06                  
---------- ---------------------- ---------------------- 
Hercules   0                      3                      
Ulysses    1                      1                      
Tazan      0                      0  



Which is excellent, exactly what I need, but I have to provide the column names 'jun06' and 'may06' 

how can I get them writen down dynamically from the query as this
is a datawarehouse and I don't want to go and add a line to the sql every end of the month. Is 
there an analytical 
function to do this?

Many thanks for all your help!

Kubilay

 


3 stars Kubilay...   July 7, 2006 - 10am Central time zone
Reviewer: A reader 
I've built a package to dynamically build the statement and then hand it back to an application.  
This is the only way that I know how to create the column names on the fly.  I'm pretty sure that 
Tom has an example in his book (I probably "borrowed" code from it). 


3 stars Column headers   July 8, 2006 - 2am Central time zone
Reviewer: Michel Cadot from France
Kubilay,

If your client is SQL*Plus and you use a SQL script, you can use something like:

-- Build the column headers you want in SQL*Plus variables
col v1 new_value h1
col v2 new_value h2
select to_char(add_months(last_day(sysdate), -1), 'mm-yy') v1,
       to_char(add_months(last_day(sysdate), -2), 'mm-yy') v2
from dual;
-- Dynamically set your headings for fixed column names
col c1 heading &h1
col c2 heading &h2
-- Then your query, renaming your column names to c1 and c2
SELECT person,
       MAX(decode(cdate, to_char(add_months(last_day(sysdate), -1), 'mm-yy'),   
cnt, 0)) c1,
       MAX(decode(cdate, to_char(add_months(last_day(sysdate), -2), 'mm-yy'),   
cnt, 0)) c2
from
  ( SELECT ta.a_txt person,
           to_char(tb.b_dat, 'mm-yy') cdate,
           count(*) cnt
    FROM ta, tb
    WHERE ta.a_id=tb.a_id
    GROUP BY ta.a_txt, tb.b_dat )
GROUP BY person
/

You can do something like that with any client application but if you want only one query I posted 
some solutions at:
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:124812348063#51542758465483
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:52266643928180#52284733269998
Regards
Michel
 


5 stars Reader & Michel   July 10, 2006 - 4am Central time zone
Reviewer: Kubilay from London , UK
Thank you very much for your response, great!

Now my 6 months data window, will be self-defining! :-))

Have a great day. 


4 stars Is it possible through sql?   July 25, 2006 - 7am Central time zone
Reviewer: A reader 
Hi Tom,
Can this be done through single sql?
create table product (cust number(10) NOT NULL, prod_cat varchar2(10) NOT NULL, product  
varchar2(10) NOT NULL);
insert into product values (1,'A1','P1');
insert into product values (1,'A1','P2');
insert into product values (1,'A2','P1');
insert into product values (2,'A1','P1');
SQL> select * from product;

      CUST PROD_CAT   PRODUCT
---------- ---------- ----------
         1 A1         P1
         1 A1         P2
         1 A2         P1
         2 A1         P1
______________
O/p required

There are three combinations as below:-


prod_cat  p1_flag p2_flag  distinct_cust_count
A1        Y         Y        1 -- for record 1 and 2 
A1        Y         N        1 -- for record 4
A2        Y         N        1 -- for record 3
Regards, 


Followup   July 25, 2006 - 11am Central time zone:

sorry, but that output really doesn't quite "make sense".  I cannot figure out why A1 would appear 
twice - why do records 1 and 2 go together and 4 is by itself (I mean, if you are breaking up by 
cust - WHY bother having distinct cust count - that would, well, always be one wouldn't it???????  
And how do you KNOW that the first A1 output record is for 1 and 2 and then next for 4 - what point 
would it be having this output if you cannot tell??)

can I get the output?  sure, but not sure if it is useful:

ops$tkyte%ORA10GR2> select prod_cat,
  2         decode( count(case when product = 'P1' then 1 end), 0, 'N', 'Y' ) p1_flag,
  3         decode( count(case when product = 'P2' then 1 end), 0, 'N', 'Y' ) p2_flag,
  4             count(distinct cust)
  5    from product
  6   group by prod_cat, cust
  7   order by prod_cat
  8  /

PROD_CAT   P P COUNT(DISTINCTCUST)
---------- - - -------------------
A1         Y Y                   1
A1         Y N                   1
A2         Y N                   1
 

3 stars Yes it is possible   July 25, 2006 - 7am Central time zone
Reviewer: Michel Cadot from France
SQL> with
  2    data as (
  3      select cust, prod_cat,
  4             max(decode(product,'P1','Y','N')) p1_flag,
  5             max(decode(product,'P2','Y','N')) p2_flag
  6      from product
  7      group by cust, prod_cat
  8    )
  9  select prod_cat, p1_flag, p2_flag, count(*)
 10  from data
 11  group by prod_cat, p1_flag, p2_flag
 12  order by prod_cat, p1_flag, p2_flag
 13  /
PROD_CAT   P P   COUNT(*)
---------- - - ----------
A1         Y N          1
A1         Y Y          1
A2         Y N          1

Michel 


4 stars Reason   July 25, 2006 - 5pm Central time zone
Reviewer: A reader 
Tom,
Thanks for your help. The reason for this is:-
How many customers own products p1 and p2 together/individually of specific category? This will 
help business to bundle products. Like your book, sometimes sold with other books?
Regards,
Tarun 


Followup   July 25, 2006 - 5pm Central time zone:

but that is not what your sample output shows!

you have it broken out by customer!!!  should your output not really have just TWO ROWS 

5 stars Can row be treated as Column   August 14, 2006 - 7am Central time zone
Reviewer: Rai from India
Hi Tom,
I have a query :->

Table Name : T1
Name            Role
 A               SD
 B               JD
 C               QE
 D               SD
 E               JD
 F               QE
Output I need :
SD       JD        QE
A         B         C
D         E         F

Note : Roles are Fixed.Only.Three - SD(Senior Developer),JD(Junior Developer),QE(Quality Engineer).

I tried this :->
SQL> Select (Decode(Role,'SD',Name)) SD,
(Decode(Role,'JD',Name)) JD,
(Decode(Role,'QE',Name)) QE
from t1
/
Out put is :->
SD    JD    QE
----- ----- -----
A
      B
            C
D
      E
            F
But I want output as followos :->
SD       JD        QE
A         B         C
D         E         F 


Followup   August 14, 2006 - 11am Central time zone:


this can quite easily be done.


scott%ORA10GR2> select rn,
  2         max( decode( job, 'CLERK', ename ) ),
  3             max( decode( job, 'MANAGER', ename ) ),
  4             max( decode( job, 'ANALYST', ename  ))
  5    from (
  6  select job, ename, row_number() over (partition by job order by ename) rn
  7    from emp
  8   where job in ( 'CLERK', 'MANAGER', 'ANALYST' )
  9         )
 10   group by rn
 11   order by rn
 12  /

        RN MAX(DECODE MAX(DECODE MAX(DECODE
---------- ---------- ---------- ----------
         1 ADAMS      BLAKE      FORD
         2 JAMES      CLARK      SCOTT
         3 MILLER     JONES
         4 SMITH

(hint, my job = your role, my ename = your name)

 

3 stars I need To Solve..   August 15, 2006 - 8am Central time zone
Reviewer: DRA from INDIA
Hi Tom,

tab1

ID, NAME, VERSION
1    a    1.1
2    b    2.1
3    c    3.1
4    d    1.1
5    e    2.0
6    f    3.0

tab2

ID LOCATION
1    L1
1    L2
1    L3
1    L4
2    L1
2    L5
2    L6
3    L7
3    L8
3    L9
4    L10
4    L11
4    L12

I want result 

ID, NAME, VERSION, WHERE_USED_COUNT, L1,L2,L3,L4.....Ln(all location)

1    A    1.1    4       ,X,X,X,X, , , 

WHERE_USED_COUNT: It count number of LOCATION used the same ID
L1,L2...LN: If the LOCATION is used for a particular ID then mark it as "X"

Please give me sql query to generete the result.

NOTE: I need the location columns should come in dyanamically as column in the result. 


Followup   August 15, 2006 - 8am Central time zone:

and I want create tables, insert intos (sort of requested on the page you used to add this?)

actually, just read this:
http://tkyte.blogspot.com/2006/08/evolution.html
that is the technique I would have used - you can just apply it to this problem 

4 stars I Need Oracle Query..   August 16, 2006 - 4am Central time zone
Reviewer: DRA from INDIA
Hi Tom...

I have MS - Access Query but i need it in Oracle(SQL)..pls help..i didn't get proper solution..

This is MS-Access Query:

TRANSFORM First(IIf(tab1.ID Is Not Null, X)) AS Check
SELECT 
    tab2.ID, 
    tab2.NAME, 
    tab2.DEP, 
    Sum(IIf(Check=X, 1)) AS TotalWhereUsed
FROM 
    tab2 LEFT JOIN tab1 ON tab2.ID = tab1.ID

GROUP BY 
    tab2.ID, 
    tab2.NAME, 
    tab2.DEP, 

ORDER BY 
    tab2.ID, 
    tab1.LOC
PIVOT 
    tab1.LOC

Mainly am facing "tab1.loc" it contains more then 50 values. How we can make it column values.

Pls help me.

Regards,
DRA 


Followup   August 16, 2006 - 8am Central time zone:

but this page shows how to pivot a result set. 

5 stars query   August 16, 2006 - 11pm Central time zone
Reviewer: mal 
Hi Tom,

Please help me to write one query or comment which one of the followin query is right? It is a part 
of debate :-)
CUSTOMER (CustomerSK, Name, Phone, EmailAddress)
STOVE (SerialNumber, Type, Version, DateofManufacture)
REGISTRATION (CustomerSK, SerialNumber, Date)
STOVE_REPAIR (RepairInvoiceNumber, SerialNumber, Date,  Description, Cost, CustomerSK)

List all the versions of stoves that are of a type "Maytag" and they have not spent any money on 
repairs.
SQL> select stve.version from stove stve,stove_repair str where stve.serialnumber=str.serialnumber 
and stve.type='MayTag' and str.cost>0;

VERSION
------------------------------
1
1


select STOVE.Version
From STOVE
Where STOVE.Type = 'Maytag'
and STOVE.SerialNumber not in
(Select STOVE_REPAIR.SerialNumber
From STOVE_REPAIR
WHERE STOVE_REPAIR.Cost > 0)
no rows selected 


Followup   August 17, 2006 - 8am Central time zone:

no primary keys
no foreign keys
no create table
no insert intos

no look.  but this sounds like homework - not material for a "debate"? 

3 stars query   August 17, 2006 - 10pm Central time zone
Reviewer: mal 
:-)   it was a home work and debate got started with class mate when she mentioned that my query is 
wrong
 


4 stars Please Correct this Query in SQL   August 24, 2006 - 5am Central time zone
Reviewer: DRA from India
Hi Tom,

Table T1 and 4 column: ID, name, value, date

ID, name, value, date
1,  A,    A1,     1/1/2006 
1,  B,    B1,     1/1/2006
1,  C,    C1,     1/1/2006

2,  A,    A2,     1/1/2006
2,  B,    A2,     1/1/2006
2,  C,    A2,     1/1/2006


3,  A,    A3,     1/1/2006
3,  B,    A3,     1/1/2006
3,  C,    A3,     1/1/2006

4,  A,    A4,     1/1/2006
4,  B,    A4,     1/1/2006
4,  C,    A4,     1/1/2006


1,  A,    A1,     2/1/2006 
1,  B,    B1,     2/1/2006
1,  C,    C1,     2/1/2006

2,  A,    A2,     2/1/2006
2,  B,    A2,     2/1/2006
2,  C,    A2,     2/1/2006


3,  A,    A3,     2/1/2006
3,  B,    A3,     2/1/2006
3,  C,    A3,     2/1/2006

4,  A,    A4,     2/1/2006
4,  B,    A4,     2/1/2006
4,  C,    A4,     2/1/2006



1,  A,    A1,     3/1/2006 
1,  B,    B1,     3/1/2006
1,  C,    C1,     3/1/2006

2,  A,    A2,     3/1/2006
2,  B,    A2,     3/1/2006
2,  C,    A2,     3/1/2006


3,  A,    A3,     3/1/2006
3,  B,    A3,     3/1/2006
3,  C,    A3,     3/1/2006

4,  A,    A4,     3/1/2006
4,  B,    A4,     3/1/2006
4,  C,    A4,     3/1/2006


I need result like :

ID,    A_JAN,    B_JAN ,    C_JAN,    A_FEB, B_FEB, C_FEB, A_MAR,  B_MAR,    C_MAR
1,    A1,    B1,    C1,    A1,    B1,    C1,  A1,    B1,    C1
2,    A2,    B2,    C2,    A2,    B2,    C2,  A2,    B2,    C2
3,    A3,    B3,    C3,    A3,    B3,    C3,  A3,    B3,    C3


Cretiria:

I need retrive data based MONTH 
    if "DATE" is JAN then if "NAME" is A then display the corresponding "VALUE" in A_JAN Column. 



I wrote the query...but some where am wrong..please correct it & send me any shotest query.


SELECT ID,    A_JAN,    B_JAN ,    C_JAN,    A_FEB, B_FEB, C_FEB, A_MAR,  B_MAR,    C_MAR FROM 

(SELECT id ID,

    CASE WHEN ( DATE = TO_DATE('1/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'A',VALUE)
    ELSE '0' 
    END AS A_JAN ,

    CASE WHEN ( DATE = TO_DATE('1/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'B',VALUE)
    ELSE '0' 
    END AS B_JAN ,

    CASE WHEN ( DATE = TO_DATE('1/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'C',VALUE)
    ELSE '0' 
    END AS C_JAN ,

    CASE WHEN ( DATE = TO_DATE('2/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'A',VALUE)
    ELSE '0' 
    END AS A_FEB ,

    CASE WHEN ( DATE = TO_DATE('2/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'B',VALUE)
    ELSE '0' 
    END AS B_FEB ,
    
    CASE WHEN ( DATE = TO_DATE('2/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'C',VALUE)
    ELSE '0' 
    END AS C_FEB ,
    
    CASE WHEN ( DATE = TO_DATE('3/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'A',VALUE)
    ELSE '0' 
    END AS  A_MAR ,

    CASE WHEN ( DATE = TO_DATE('3/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'B',VALUE)
    ELSE '0' 
    END AS  B_MAR ,
    
    CASE WHEN ( DATE = TO_DATE('3/1/2006','mm/dd/YYYY') ) 
    THEN DECODE(NAME,    'C',VALUE)
    ELSE '0' 
    END AS  C_MAR 



FROM 
    T1) t
 WHERE 
    A_JAN!='0' AND B_JAN!='0' AND C_JAN!='0' AND A_FEB!='0' AND B_FEB!='0' AND C_FEB!='0' AND 
A_MAR!='0' AND B_MAR!='0' AND C_MAR!='0'  
ORDER BY t.id

Thanks in Advance.
DRA
 


Followup   August 27, 2006 - 7pm Central time zone:

no create table
no insert into table
no look 

5 stars Almost works for my sitiuation   September 6, 2006 - 5pm Central time zone
Reviewer: Jay Kiefer from boston, ma
I am trying to use your example but I am not able to adjust for the columns.   Mine too has a set 
cardinality (12).  However I want the columns to pivot to rows.  In your example it pivots on Jobs. 
  

The union gives me the exact answer I want.   The reason I'm asking the question is that there must 
be a better way since I expect there is a limit to how many union alls I can do. There is one union 
for each color (column). 
 
I've tried using all_tab_columns to help me pivot but I can't seem to make that work.

SELECT column_name, COLUMN_ID 
FROM all_tab_columns atc
WHERE table_name = 'JK_DEMO_DATA'


CREATE TABLE JK_DEMO_DATA
(calendar_year VARCHAR2(4)
 , month_number VARCHAR2(2)
 , month_mon    VARCHAR2(3)
 , red NUMBER
 , orange NUMBER
 , yellow NUMBER
 , blue NUMBER
 , green NUMBER);
 
 
BEGIN
INSERT INTO JK_DEMO_DATA VALUES('2005','1','JAN',1,2,3,4,5);
INSERT INTO JK_DEMO_DATA VALUES('2005','2','FEB',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2005','3','MAR',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2005','4','APR',1,2,3,4,5);
INSERT INTO JK_DEMO_DATA VALUES('2005','5','MAY',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2005','6','JUN',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2005','7','JUL',1,2,3,4,5);
INSERT INTO JK_DEMO_DATA VALUES('2005','8','AUG',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2005','9','SEP',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2005','10','OCT',1,2,3,4,5);
INSERT INTO JK_DEMO_DATA VALUES('2005','11','NOV',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2005','12','DEC',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2006','1','JAN',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2006','2','FEB',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2006','3','MAR',10,11,12,13,14);
INSERT INTO JK_DEMO_DATA VALUES('2006','4','APR',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2006','5','MAY',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2006','6','JUN',10,11,12,13,14);
INSERT INTO JK_DEMO_DATA VALUES('2006','7','JUL',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2006','8','AUG',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2006','9','SEP',10,11,12,13,14);
INSERT INTO JK_DEMO_DATA VALUES('2006','10','OCT',4,5,6,7,8);
INSERT INTO JK_DEMO_DATA VALUES('2006','11','NOV',7,8,9,10,11);
INSERT INTO JK_DEMO_DATA VALUES('2006','12','DEC',10,11,12,13,14);
END;



SELECT CALENDAR_YEAR, DESCR
  ,  SUM(JAN)  JAN
  ,  SUM(FEB)  FEB
  ,  SUM(MAR)  MAR
  ,  SUM(APR)  APR
  ,  SUM(MAY)  MAY
  ,  SUM(JUN)  JUN
  ,  SUM(JUL)  JUL
  ,  SUM(AUG)  AUG
  ,  SUM(SEP)  SEP
  ,  SUM(OCT)  OCT
  ,  SUM(NOV)  NOV
  ,  SUM(DEC)  DEC
FROM (  
SELECT CALENDAR_YEAR, 'red' DESCR, 1 PLACEHOLDER
,  ( CASE WHEN jk.month_number  ='1' THEN  jk.red ELSE NULL END) JAN
,  ( CASE WHEN jk.month_number  ='2' THEN  jk.red ELSE NULL END) FEB
,  ( CASE WHEN jk.month_number  ='3' THEN  jk.red ELSE NULL END) MAR
,  ( CASE WHEN jk.month_number  ='4' THEN  jk.red ELSE NULL END) APR
,  ( CASE WHEN jk.month_number  ='5' THEN  jk.red ELSE NULL END) MAY
,  ( CASE WHEN jk.month_number  ='6' THEN  jk.red ELSE NULL END) JUN
,  ( CASE WHEN jk.month_number  ='7' THEN  jk.red ELSE NULL END) JUL
,  ( CASE WHEN jk.month_number  ='8' THEN  jk.red ELSE NULL END) AUG
,  ( CASE WHEN jk.month_number  ='9' THEN  jk.red ELSE NULL END) SEP
,  ( CASE WHEN jk.month_number  ='10' THEN  jk.red ELSE NULL END) OCT
,  ( CASE WHEN jk.month_number  ='11' THEN  jk.red ELSE NULL END) NOV
,  ( CASE WHEN jk.month_number  ='12' THEN  jk.red ELSE NULL END) DEC
FROM JK_DEMO_DATA jk
UNION ALL
SELECT CALENDAR_YEAR, 'orange' DESCR, 2 PLACEHOLDER
,  ( CASE WHEN jk.month_number  ='1' THEN  jk.orange ELSE NULL END) JAN
,  ( CASE WHEN jk.month_number  ='2' THEN  jk.orange ELSE NULL END) FEB
,  ( CASE WHEN jk.month_number  ='3' THEN  jk.orange ELSE NULL END) MAR
,  ( CASE WHEN jk.month_number  ='4' THEN  jk.orange ELSE NULL END) APR
,  ( CASE WHEN jk.month_number  ='5' THEN  jk.orange ELSE NULL END) MAY
,  ( CASE WHEN jk.month_number  ='6' THEN  jk.orange ELSE NULL END) JUN
,  ( CASE WHEN jk.month_number  ='7' THEN  jk.orange ELSE NULL END) JUL
,  ( CASE WHEN jk.month_number  ='8' THEN  jk.orange ELSE NULL END) AUG
,  ( CASE WHEN jk.month_number  ='9' THEN  jk.orange ELSE NULL END) SEP
,  ( CASE WHEN jk.month_number  ='10' THEN  jk.orange ELSE NULL END) OCT
,  ( CASE WHEN jk.month_number  ='11' THEN  jk.orange ELSE NULL END) NOV
,  ( CASE WHEN jk.month_number  ='12' THEN  jk.orange ELSE NULL END) DEC
FROM JK_DEMO_DATA jk
UNION ALL
SELECT CALENDAR_YEAR, 'yellow' DESCR, 3 PLACEHOLDER
,  ( CASE WHEN jk.month_number  ='1' THEN  jk.yellow ELSE NULL END) JAN
,  ( CASE WHEN jk.month_number  ='2' THEN  jk.yellow ELSE NULL END) FEB
,  ( CASE WHEN jk.month_number  ='3' THEN  jk.yellow ELSE NULL END) MAR
,  ( CASE WHEN jk.month_number  ='4' THEN  jk.yellow ELSE NULL END) APR
,  ( CASE WHEN jk.month_number  ='5' THEN  jk.yellow ELSE NULL END) MAY
,  ( CASE WHEN jk.month_number  ='6' THEN  jk.yellow ELSE NULL END) JUN
,  ( CASE WHEN jk.month_number  ='7' THEN  jk.yellow ELSE NULL END) JUL
,  ( CASE WHEN jk.month_number  ='8' THEN  jk.yellow ELSE NULL END) AUG
,  ( CASE WHEN jk.month_number  ='9' THEN  jk.yellow ELSE NULL END) SEP
,  ( CASE WHEN jk.month_number  ='10' THEN  jk.yellow ELSE NULL END) OCT
,  ( CASE WHEN jk.month_number  ='11' THEN  jk.yellow ELSE NULL END) NOV
,  ( CASE WHEN jk.month_number  ='12' THEN  jk.yellow ELSE NULL END) DEC
FROM JK_DEMO_DATA jk
UNION ALL
SELECT CALENDAR_YEAR, 'green' DESCR, 4 PLACEHOLDER
,  ( CASE WHEN jk.month_number  ='1' THEN  jk.green ELSE NULL END) JAN
,  ( CASE WHEN jk.month_number  ='2' THEN  jk.green ELSE NULL END) FEB
,  ( CASE WHEN jk.month_number  ='3' THEN  jk.green ELSE NULL END) MAR
,  ( CASE WHEN jk.month_number  ='4' THEN  jk.green ELSE NULL END) APR
,  ( CASE WHEN jk.month_number  ='5' THEN  jk.green ELSE NULL END) MAY
,  ( CASE WHEN jk.month_number  ='6' THEN  jk.green ELSE NULL END) JUN
,  ( CASE WHEN jk.month_number  ='7' THEN  jk.green ELSE NULL END) JUL
,  ( CASE WHEN jk.month_number  ='8' THEN  jk.green ELSE NULL END) AUG
,  ( CASE WHEN jk.month_number  ='9' THEN  jk.green ELSE NULL END) SEP
,  ( CASE WHEN jk.month_number  ='10' THEN  jk.green ELSE NULL END) OCT
,  ( CASE WHEN jk.month_number  ='11' THEN  jk.green ELSE NULL END) NOV
,  ( CASE WHEN jk.month_number  ='12' THEN  jk.green ELSE NULL END) DEC
FROM JK_DEMO_DATA jk
)
--WHERE calendar_year = '2006'
GROUP BY CALENDAR_YEAR, DESCR, placeholder
ORDER BY CALENDAR_YEAR, placeholder 


Followup   September 7, 2006 - 7am Central time zone:

turn the columns into the rows (they should have been from day one apparently, your data model is 
not 'good' that way) by cartesian joining to a set that has as many rows as you have columns to 
"unpivot" (any 5 row table will do, I made up one on the fly here)

then a simple pivot against that fixed up data set:

ops$tkyte%ORA10GR2> with data
  2  as
  3  (select level l from dual connect by level <= 5)
  4  SELECT CALENDAR_YEAR, DESCR, PLACEHOLDER
  5  ,  sum( CASE WHEN month_number  ='1' THEN  val ELSE NULL END) JAN
  6  ,  sum( CASE WHEN month_number  ='2' THEN  val ELSE NULL END) FEB
  7  ,  sum( CASE WHEN month_number  ='3' THEN  val ELSE NULL END) MAR
  8  ,  sum( CASE WHEN month_number  ='4' THEN  val ELSE NULL END) APR
  9  ,  sum( CASE WHEN month_number  ='5' THEN  val ELSE NULL END) MAY
 10  ,  sum( CASE WHEN month_number  ='6' THEN  val ELSE NULL END) JUN
 11  ,  sum( CASE WHEN month_number  ='7' THEN  val ELSE NULL END) JUL
 12  ,  sum( CASE WHEN month_number  ='8' THEN  val ELSE NULL END) AUG
 13  ,  sum( CASE WHEN month_number  ='9' THEN  val ELSE NULL END) SEP
 14  ,  sum( CASE WHEN month_number  ='10' THEN  val ELSE NULL END) OCT
 15  ,  sum( CASE WHEN month_number  ='11' THEN  val ELSE NULL END) NOV
 16  ,  sum( CASE WHEN month_number  ='12' THEN  val ELSE NULL END) DEC
 17  from (
 18  select calendar_year,
 19         month_number,
 20         l placeholder,
 21             decode( l, 1, 'red',
 22                        2, 'orange',
 23                                    3, 'yellow',
 24                                    4, 'green',
 25                                    5, 'blue' ) descr,
 26             decode( l, 1, red,
 27                        2, orange,
 28                                    3, yellow,
 29                                    4, green,
 30                                    5, blue ) val
 31    from t, data
 32  )
 33  group by calendar_year, descr, placeholder
 34  order by calendar_year, placeholder
 35  /
 

5 stars Perfect; Thank you!   September 7, 2006 - 10am Central time zone
Reviewer: Jay Kiefer from Boston, MA USA
I appreciate your response and your commitment to this site! 


5 stars Simple question   September 7, 2006 - 6pm Central time zone
Reviewer: Alex from Europe, Estonia
If I understand correctly, there is no way to convert all or some of the rows from one table, to 
columns in some sql query?
 
Thanks,
Alex 


Followup   September 8, 2006 - 4pm Central time zone:

I'm not even sure what that would look like, using SCOTT.DEPT table - tell us what that "means" by 
way of an example (eg: this is what I expect as output) 

5 stars Example   September 7, 2006 - 6pm Central time zone
Reviewer: Alex from Europe, Estonia
For example:
col1 col2
a      1
b      2
c      3
...etc
is it possible to create something like this, without hardcoding every column, only with pure sql? 
(i don't have permissions to create new tables, views or packages)
=>
a b c
1 2 3

Thanks,
Alex 


Followup   September 8, 2006 - 4pm Central time zone:

you would have to use SQL to write SQL here.

You would have to query to find the number of rows in the table.
You would have to query to find the number of columns in the table.

then you could write a query to pivot it all and open that result set with dynamic sql.

line 3: number of columns is what drives the level <= 3
lines 5-8 - determined by number of rows in the table
lines 10-11 - built based on number of columns

ops$tkyte%ORA10GR2> with data
  2  as
  3  (select level line from dual connect by level <= 3)
  4  select cname,
  5         max( decode( r, 1, val ) ) c1,
  6         max( decode( r, 2, val ) ) c2,
  7         max( decode( r, 3, val ) ) c3,
  8         max( decode( r, 4, val ) ) c4
  9    from (
 10  select decode( line, 1, 'deptno', 2, 'dname', 3, 'loc' ) cname,
 11         decode( line, 1, to_char(deptno), 2, dname, 3, loc ) val,
 12             r, line
 13   from ( select dept.*, rownum r
 14            from dept ),
 15            data
 16            )
 17   group by cname, line
 18   order by line
 19  /

CNAME  C1         C2         C3         C4
------ ---------- ---------- ---------- ----------
deptno 10         20         30         40
dname  ACCOUNTING RESEARCH   SALES      OPERATIONS
loc    NEW YORK   DALLAS     CHICAGO    BOSTON
 

5 stars execlent examples easy to follow   September 14, 2006 - 11am Central time zone
Reviewer: A reader 
Thanks to Tom et Al. 


5 stars execlent examples - easy to follow   September 14, 2006 - 12pm Central time zone
Reviewer: Dave from Alberta, Canada
Thanks to Tom et Al. 


5 stars very interesting discussion   September 14, 2006 - 4pm Central time zone
Reviewer: A reader 
Tom,

I was wondering if there is a better way to re write this query. I want to be able to elimiate the 
union all and therefore you not doing a double pass on the data. I was
thinking maybe using a pivot but the query is in a way pivoted Any ideas??

SELECT R.FINALIZED_DATE FINALIZE_DATE, R.RAIL_LINE_CODE RAILLINE_CODE,                      
DECODE(R.DIRECTION,1,DECODE(R.STATUS_CODE,'E',0,
                
DECODE(UPPER(R.EDI_W4_STATE),'AB',1,'BC',1,'MB',1,'NB',1,'NF',1,'NT',1,'NS',1,'ON',1,
                                                 
'PE',1,'PQ',1,'SK',1,'YT',1,0)),0)EASTBOUND_CANADIAN_FULL,
DECODE(R.DIRECTION,1,DECODE(R.STATUS_CODE,'E',
                     DECODE(UPPER(R.EDI_W4_STATE),'AB',1,'BC',1,'MB',1,'NB',1,'NF',1,'NT',1,
                             
'NS',1,'ON',1,'PE',1,'PQ',1,'SK',1,'YT',1,0),0),0)EASTBOUND_CANADIAN_EMPTY,
DECODE(R.DIRECTION,1,DECODE(R.STATUS_CODE,'E',0,
                     DECODE(UPPER(R.EDI_W4_STATE),'AB',0,'BC',0,'MB',0,'NB',0,'NF',0,'NT',0,
                             
'NS',0,'ON',0,'PE',0,'PQ',0,'SK',0,'YT',0,1)),0)EASTBOUND_MIDWEST_FULL, 
DECODE(R.DIRECTION,1,DECODE(R.STATUS_CODE,'E',
                     DECODE(UPPER(R.EDI_W4_STATE),'AB',0,'BC',0,'MB',0,'NB',0,'NF',0,'NT',0,
                             
'NS',0,'ON',0,'PE',0,'PQ',0,'SK',0,'YT',0,1),0),0)EASTBOUND_MIDWEST_EMPTY,                          
   
DECODE(R.DIRECTION,0,1,0)WESTBOUND,  
DECODE(R.DIRECTION,0,DECODE(R.STATUS_CODE,'E',0,
                     DECODE(SUBSTR(R.COUNTRY,1,4),'CANA',1,0)),0)WESTBOUND_CANADIAN_FULL,  
DECODE(R.DIRECTION,0,DECODE(R.STATUS_CODE,'E',
                     DECODE(SUBSTR(R.COUNTRY,1,4),'CANA',1,0),0),0)WESTBOUND_CANADIAN_EMPTY,  
DECODE(R.DIRECTION,0,DECODE(R.STATUS_CODE,'E',0,
                     DECODE(SUBSTR(R.COUNTRY,1,4),'CANA',0,1)),0)WESTBOUND_MIDWEST_FULL,  
DECODE(R.DIRECTION,0,DECODE(R.STATUS_CODE,'E',
                     DECODE(SUBSTR(R.COUNTRY,1,4),'CANA',0,1),0),0)WESTBOUND_MIDWEST_EMPTY  
FROM new_table R
UNION ALL
SELECT RA.FINALIZED_DATE FINALIZE_DATE, 
RA.RAIL_LINE_CODE RAILLINE_CODE,                      
DECODE(RA.DIRECTION,1,DECODE(RA.STATUS_CODE,'E',0,
                     
DECODE(UPPER(RA.EDI_W4_STATE),'AB',1,'BC',1,'MB',1,'NB',1,'NF',1,'NT',1,'NS',1,'ON',1,
                                                 
'PE',1,'PQ',1,'SK',1,'YT',1,0)),0)EASTBOUND_CANADIAN_FULL,
DECODE(RA.DIRECTION,1,DECODE(RA.STATUS_CODE,'E',
                     DECODE(UPPER(RA.EDI_W4_STATE),'AB',1,'BC',1,'MB',1,'NB',1,'NF',1,'NT',1,
                             
'NS',1,'ON',1,'PE',1,'PQ',1,'SK',1,'YT',1,0),0),0)EASTBOUND_CANADIAN_EMPTY,
DECODE(RA.DIRECTION,1,DECODE(RA.STATUS_CODE,'E',0,
                     DECODE(UPPER(RA.EDI_W4_STATE),'AB',0,'BC',0,'MB',0,'NB',0,'NF',0,'NT',0,
                             
'NS',0,'ON',0,'PE',0,'PQ',0,'SK',0,'YT',0,1)),0)EASTBOUND_MIDWEST_FULL, 
DECODE(RA.DIRECTION,1,DECODE(RA.STATUS_CODE,'E',
                     DECODE(UPPER(RA.EDI_W4_STATE),'AB',0,'BC',0,'MB',0,'NB',0,'NF',0,'NT',0,
                             
'NS',0,'ON',0,'PE',0,'PQ',0,'SK',0,'YT',0,1),0),0)EASTBOUND_MIDWEST_EMPTY,                          
   
DECODE(RA.DIRECTION,0,1,0)WESTBOUND,  
DECODE(RA.DIRECTION,0,DECODE(RA.STATUS_CODE,'E',0,
                     DECODE(SUBSTR(RA.COUNTRY,1,4),'CANA',1,0)),0)WESTBOUND_CANADIAN_FULL,  
DECODE(RA.DIRECTION,0,DECODE(RA.STATUS_CODE,'E',
                     DECODE(SUBSTR(RA.COUNTRY,1,4),'CANA',1,0),0),0)WESTBOUND_CANADIAN_EMPTY,  
DECODE(RA.DIRECTION,0,DECODE(RA.STATUS_CODE,'E',0,
                     DECODE(SUBSTR(RA.COUNTRY,1,4),'CANA',0,1)),0)WESTBOUND_MIDWEST_FULL,  
DECODE(RA.DIRECTION,0,DECODE(RA.STATUS_CODE,'E',
                     DECODE(SUBSTR(RA.COUNTRY,1,4),'CANA',0,1),0),0)WESTBOUND_MIDWEST_EMPTY  
FROM new_table RA 


 


Followup   September 14, 2006 - 4pm Central time zone:

looks like you want each row output "twice"


select FINALIZED_DATE FINALIZE_DATE, 
       RAIL_LINE_CODE RAILLINE_CODE, 
       case when r = 1 
            then DECODE-FROM-FIRST-QUERY
            else DECODE-FROM-SECOND-QUERY
        end EASTBOUND_CANADIAN_FULL, 
       case when r = 1
            then ..
            else ...
        end ....
  from new_table, (select 1 r from dual union all select 2 r from dual) 

5 stars GREAT!!!   September 15, 2006 - 10am Central time zone
Reviewer: A reader 
Tom,

The approach was great. I forgot to mention that
the reason why I have the second union is that it is 
from different table let's table b in the last
query. 


Followup   September 15, 2006 - 10am Central time zone:

eh?  if you have two separate tables then you are not making a "double pass". 

now you've confused me entirely. 

4 stars Row values in single column   September 21, 2006 - 2pm Central time zone
Reviewer: Jonty from NY
Hi,

Dept, Emp columns in a table.

I want to get results like

Dept1, 'emp1, emp2, emp3'
Dept2, 'emp11, emp22'
Dept3, 'emp9'

How can I get this ?

Response will be highly appreciated.

Thanks

Jonty 


Followup   September 22, 2006 - 2am Central time zone:

search this site for stragg 

3 stars ok   September 26, 2006 - 6am Central time zone
Reviewer: Ravikumar from Manchester,UK
Hi Tom,
I am not sure why the sum(decode(... function returns the same values as that
of original column values.I thought there would be a change in column values after
applying the sum(decode(... function.
what can be the use of sum(decode(.. function?

SQL> create table t (x number);
Table Created.

SQL> insert into t values(10);
1 row created.

SQL> insert into t values(20);
1 row created.

SQL> insert into t values(50);
1 row created.

SQL> insert into t values(100);
1 row created.

SQL> select * from t
  2  /

         X
----------
        10
        20
        50
       100

4 rows selected.

SQL> select x,sum(decode(sign(x),1,x,-x)) as val
  2  from t
  3  group by x
  4  /

         X        VAL
---------- ----------
        10         10
        20         20
        50         50
       100        100

4 rows selected.

Please do reply. 


Followup   September 26, 2006 - 3pm Central time zone:

I don't know why you would thing "values would change".  so you'll have to explain what you thought 
should happen - and WHY.

You have 4 rows in the input set
they all have unique X= values
group by returns unique group by values
you group by X

hence 4 rows in the output... and the sum of x, given they are all positive, would be X in this 
case... 

5 stars Pivoting   October 4, 2006 - 4pm Central time zone
Reviewer: hari bhaskar gupta from usa
It is very  very good for it helpfull 


5 stars performance issue on pivot query   October 10, 2006 - 11am Central time zone
Reviewer: Franco Ropelato from Roma, Italy
Hi Tom,

Thanks a lot for the site!

I noticed that the execution time of the pivot query is highly dependent on the number of the 
columns of the pivot.
i.e., this query:
   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2
   FROM
      t_fv
   GROUP BY code;
takes 5 sec on my system, while this:
   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3,
      max(decode(fld0,4,fld1,NULL)) AS x4,
      max(decode(fld0,5,fld1,NULL)) AS x5,
      max(decode(fld0,6,fld1,NULL)) AS x6
   FROM
      t_fv
   GROUP BY code;
takes 150 seconds!

Why this is happening? The execution plans, obviously, are the same; it seems to me that the only 
difference is a matter of numbers of "if"s in the execution code.
The run_stats package tells that in the 2nd query there are a lot more physical reads and writes, 
why that?
Should I investigate on poor values of some parameter of my db instance?

Investigating further, these are the figures for a growing number of result columns:
columns    time in seconds
   1             4
   2             5
   3            64
   4           101
   5           121
   6           150
The strange thing: the big bump from 3rd...



There is the "build code" for this analysis:

CREATE TABLE t_fv (
    code NUMBER(10,0),
    fld0 NUMBER(10,0),
    fld1 VARCHAR2(250));

INSERT INTO t_fv
SELECT rownum, 1, object_name FROM all_objects;
COMMIT;
INSERT INTO t_fv
SELECT rownum, 2, object_name FROM all_objects;
COMMIT;
INSERT INTO t_fv
SELECT rownum, 3, object_name FROM all_objects;
COMMIT;
INSERT INTO t_fv
SELECT rownum, 4, object_name FROM all_objects;
COMMIT;
INSERT INTO t_fv
SELECT rownum, 5, object_name FROM all_objects;
COMMIT;
INSERT INTO t_fv
SELECT rownum, 6, object_name FROM all_objects;
COMMIT;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1
   FROM
      t_fv
   GROUP BY code;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2
   FROM
      t_fv
   GROUP BY code;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3
   FROM
      t_fv
   GROUP BY code;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3,
      max(decode(fld0,4,fld1,NULL)) AS x4
   FROM
      t_fv
   GROUP BY code;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3,
      max(decode(fld0,4,fld1,NULL)) AS x4,
      max(decode(fld0,5,fld1,NULL)) AS x5
   FROM
      t_fv
   GROUP BY code;

   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3,
      max(decode(fld0,4,fld1,NULL)) AS x4,
      max(decode(fld0,5,fld1,NULL)) AS x5,
      max(decode(fld0,6,fld1,NULL)) AS x6
   FROM
      t_fv
   GROUP BY code;
 


Followup   October 10, 2006 - 12pm Central time zone:

tkprof it, what does that show.... 

5 stars performance issue on pivot query   October 10, 2006 - 1pm Central time zone
Reviewer: Franco Ropelato from Roma, Italy
If I understand your request correctly, this is the tkprof output:

CREATE TABLE t_zz1 AS
   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1
      , CAST('xxx' as varchar2(250)) AS x2
      , CAST('xxx' as varchar2(250)) AS x3
      , CAST('xxx' as varchar2(250)) AS x4
      , CAST('xxx' as varchar2(250)) AS x5
      , CAST('xxx' as varchar2(250)) AS x6
   FROM
      t_fv
   GROUP BY code

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      1.83       1.95       1512       1616        224       25425
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.83       1.95       1512       1617        224       25425

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 107  (ASST_SVIL2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  CREATE TABLE STATEMENT   GOAL: FIRST_ROWS
      0   LOAD AS SELECT
      0    SORT (GROUP BY)
      0     TABLE ACCESS (FULL) OF 'T_FV'

********************************************************************************

CREATE TABLE t_zz6 AS
   SELECT
      code,
      max(decode(fld0,1,fld1,NULL)) AS x1,
      max(decode(fld0,2,fld1,NULL)) AS x2,
      max(decode(fld0,3,fld1,NULL)) AS x3,
      max(decode(fld0,4,fld1,NULL)) AS x4,
      max(decode(fld0,5,fld1,NULL)) AS x5,
      max(decode(fld0,6,fld1,NULL)) AS x6
   FROM
      t_fv
   GROUP BY code

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1     21.69      33.85      77788       1622        270       25425
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2     21.69      33.85      77788       1623        270       25425

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 107  (ASST_SVIL2)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  CREATE TABLE STATEMENT   GOAL: FIRST_ROWS
      0   LOAD AS SELECT
      0    SORT (GROUP BY)
      0     TABLE ACCESS (FULL) OF 'T_FV'

******************************************************************************** 


Followup   October 10, 2006 - 8pm Central time zone:

I don't trust explain plan, can you

a) log into sqlplus
b) enable trace
c) run the sql statements
d) EXIT sqlplus (to close all cursors)
e) then tkprof. 

5 stars Single Column into row   October 12, 2006 - 9am Central time zone
Reviewer: A reader 
How should I best turn a query that will always return a single row, e.g. select 1,2,3 from dual;
which returns:

1            2             3

into one which returns separate rows, e.g.

1
2
3


? 


Followup   October 12, 2006 - 11am Central time zone:

ops$tkyte%ORA10GR2> with data as (select level l from dual connect by level <= 3)
  2  select decode( l, 1, c1, 2, c2, 3, c3 )
  3   from (
  4  select 1 c1, 2 c2, 3 c3, l
  5    from dual, data
  6  )
  7  /

DECODE(L,1,C1,2,C2,3,C3)
------------------------
                       1
                       2
                       3

 

5 stars Pivot filtering by single column value   October 12, 2006 - 4pm Central time zone
Reviewer: Mike Thomas from Raleigh, NC
Take your first example:

scott@DEV816> 
scott@DEV816> select job,
  2         max( decode( deptno, 10, cnt, null ) ) dept_10,
  3         max( decode( deptno, 20, cnt, null ) ) dept_20,
  4         max( decode( deptno, 30, cnt, null ) ) dept_30,
  5         max( decode( deptno, 40, cnt, null ) ) dept_40
  6    from ( select job, deptno, count(*) cnt
  7             from emp
  8            group by job, deptno )
  9   group by job
 10  /

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

I need only the column sets of pivoted data for where DEPT_10 = 1. E.g. DEPT_10 through DEPT_40 for 
CLERK, MANAGER, and PRESIDENT. I am only trying to filter by one column, e.g. DEPT_10.

I could not think of WHERE clause syntax for the outer query  that would return DEPT_10 = 1 data 
sets for CLERK, MANAGER, PRESIDENT. E.g. "WHERE max( decode( deptno, 10, cnt, null ) ) = 1" gives 
an syntax error.

If possible, I do _not_ want to wrap the statement, e.g.
select *
from (<all above>)
where DEPT_10 = 1;

I suspect its possible to use analytical or group in sub-query to test for one column's dataset 
DEPT_10 = 1. Then, I might then be able to filter just the set of sub-query values that contribute 
to the pivot DEPT_10 = 1.

I hope there might be a performance difference between wrapping with SELECT * FROM () WHERE DEPT_10 
= 1 and the analytical/group of sub-query technique.

Is this possible?  


Followup   October 13, 2006 - 6am Central time zone:

sorry, you do want to wrap that statement in the manner you say you do not want to.

why _dont_ you want to????????????  you don't want to get the answer?? 

5 stars Pivot filtering by single column value (followup)   October 12, 2006 - 6pm Central time zone
Reviewer: Mike Thomas from Raleigh, NC
I got something working. I'm not sure if it improves or worsens performance as compared to a simple 
wrapper like SELECT * FROM (<ORIG SQL>) WHERE dept_10 = 1. But, it works without a wrapper which 
implies it might not need to return values for dept_20 through dept_40 and might save LIO.

--
select job,
max ( decode( deptno, 10, cnt, null ) ) dept_10,
max ( decode( deptno, 20, cnt, null ) ) dept_20,
max ( decode( deptno, 30, cnt, null ) ) dept_30,
max ( decode( deptno, 40, cnt, null ) ) dept_40
from ( select job, deptno, count(*) cnt,
         count( decode( deptno, 10, 1, null ) ) over ( partition by job order by job ) deptno_x
         from emp
       group by job, deptno )
where deptno_x = 1
  and cnt = 1
group by job
order by job;
--
Note: Two parts. 
  First "deptno_x = 1" determines pivotset for deptno = 10. 
  Second "cnt = 1" determines pivot value.
--

I'll have to test performance with a larger dataset, like my big pivot queries at work. 


Followup   October 13, 2006 - 7am Central time zone:

(hint: the inline view is, well, better - you have to materialize the set anyway, the inline view 
is much easier to digest in this case don't you think) 

5 stars minor SQL correction, sorry   October 12, 2006 - 7pm Central time zone
Reviewer: Mike Thomas from Raleigh, NC
--
select job,
max ( decode( deptno, 10, cnt, null ) ) dept_10,
max ( decode( deptno, 20, cnt, null ) ) dept_20,
max ( decode( deptno, 30, cnt, null ) ) dept_30,
max ( decode( deptno, 40, cnt, null ) ) dept_40
from ( select job, deptno, count(*) cnt,
        sum( decode( deptno, 10, count(*), null ) ) 
        over ( partition by job order by job ) deptno_value
         from emp
       group by job, deptno )
where deptno_value = 1
group by job
order by job;
--
Note: deptno_value = 1, using count(*) for depno = 10
--
 


5 stars agree   October 13, 2006 - 12pm Central time zone
Reviewer: Mike Thomas from Raleigh, NC
<QUOTE>
(hint: the inline view is, well, better - you have to materialize the set anyway, the inline view 
is much easier to digest in this case don't you think) 
</QUOTE>

I agree with you about being easier to digest, and that we have to materialize the set anyway. I'm 
not sure the temp space requirements are the same with both, so I'm checking with performance 
tests.

The reason for _not_ doing, as I requested above, is there are existing stored procedures 
generating dynamic sql, and the pivot max/decodes are dynamically generated as a wrapper to a user 
generated sub-query. First, plsql determines how many and what rows/columns to pivot, and second 
dynamically generates the SQL. This extends the features in your book by allowing dynamic numbers 
of columns based on sub-query data.

So, basically, I'm using the "harder to understand" technique, but without any stored procedure 
code changes - some business benefit. And, I'm testing both SQL to deterimine if there is possibly 
any LIO/Sort differences between the two SQL. I suspect only TEMP space benefit, but won't know 
till I see tkprof on my larger datasets.

Thanks for your site, and help. This is an awesome website - I would never have gotten this far 
without your help! 


2 stars Pivot table oracle with unknown number of rows to be pivot   October 31, 2006 - 6pm Central time zone
Reviewer: Prince from Ca
Tom 
I read each line on this page but couldn't find sol. I tried
several methods before could not succed. My Problem is 
 
DATE_RT          CODE         QTY
27-SEP-2006       1444        .01
27-SEP-2006       2924         .9
27-SEP-2006       2924        .45
26-JUL-2006       2372        .05
26-JUL-2006       1444        .01
26-JUL-2006       2966       .025
26-JUL-2006       2074          1
26-JUL-2006       2810        .01
26-JUL-2006       3270       .015
26-JUL-2006       2372        .05
26-JUL-2006       1444        .01
26-JUL-2006       2966       .025
26-JUL-2006       2074          1
26-JUL-2006       2810        .01
26-JUL-2006       3270       .015
26-JUL-2006       3426         .5
26-JUL-2006       3426          1
26-JUL-2006       3426        4.5
26-JUL-2006       3426          1
29-AUG-2006       2074         .1
29-AUG-2006       1444        .01
29-AUG-2006       2372       .005
09-OCT-2006       3270       .055
07-OCT-2006       2924          4
07-OCT-2006       3426          2
07-OCT-2006       2810        .01
07-OCT-2006       1444        .02
07-OCT-2006       3426         .5
07-OCT-2006       2924        .45
07-OCT-2006       2924        .45
07-OCT-2006       3426          1
07-OCT-2006       3426          2
07-OCT-2006       2372        .05
07-OCT-2006       3426        2.5
07-OCT-2006       2966       .025
07-OCT-2006       1444        1
07-OCT-2006       2810        2
29-AUG-2006       2074        5

Desired Result should be like  : 
DATE         1444 2074 2372 2810 2924 2966 3426 ......
07-OCT-2006  1.02      .05  2.01 4.90  .............
...
29-AUG-2006   .01 5.1  .005  ..........

   
my p_anchor is date
my pivot col is  (code) And Qty which is sum of QTY of same code on the same date as displayed 
above.
The number off code varies so i am not sure how many cols they can.     

I tried 
variable x refcursor
set autoprint on

begin
     my_pkg.pivot
     ( p_max_cols_query => 'select count (distinct (code)) from TT',
      p_query => 'select DATE_RT, code, sum(QTY) SUMQTY, row_number()  over ( partition by 
DATE_RT order by CODE) rn from TT group by DATE_RT, code',
       p_anchor => my_pkg.array( 'DATE_RT','SUMQTY'),
       p_pivot  => my_pkg.array( 'code'),
        p_cursor => :x );
  end;

with tried ur my_pivot package but not successful.
please suggest where the package needs to be changed.
l_query := l_query ||
                  'max(decode(rn,'||i||','||
                             p_pivot(j)||',null)) ' ||
                             p_pivot(j) || '_' || i || ',';


 


Followup   October 31, 2006 - 6pm Central time zone:

you would have to 

a) run a query to determine the number of columns
b) use that information to  build a dynamic sql query that does that. 

2 stars pivot table oracle using ref cursor   October 31, 2006 - 8pm Central time zone
Reviewer: prince from Ca
CREATE TABLE DEMO_DATA
(DATE_RT    DATE
 , code NUMBER
 , qty NUMBER (10,4)
 );
INSERT INTO DEMO_DATA  VALUES ('27-SEP-2006',1444,.01);
INSERT INTO DEMO_DATA VALUES ('27-SEP-2006',2924,9);
INSERT INTO DEMO_DATA VALUES ('27-SEP-2006',2924,.45);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2372,.05);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',1444,.01);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2966,.025);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2074,1);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2810,.01);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3270,.015);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2372,.05);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',1444,.01);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2966,.025);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2074,1);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',2810,.01);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3270,.015);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3426,.5);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3426,1);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3426,4.5);
INSERT INTO DEMO_DATA VALUES ('26-JUL-2006',3426,1);
INSERT INTO DEMO_DATA VALUES ('29-AUG-2006',2074,.1);
INSERT INTO DEMO_DATA VALUES ('29-AUG-2006',1444,.01);
INSERT INTO DEMO_DATA VALUES ('29-AUG-2006',2372,.005);
INSERT INTO DEMO_DATA VALUES ('09-OCT-2006',3270,.055);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2924,4);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3426,2);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2810,.01);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',1444,.02);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3426,.5);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2924,.45);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2924,.45);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3426,1);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3426,2);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3372,.05);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',3426,2.5);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2966,.025);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',1444,1);
INSERT INTO DEMO_DATA VALUES ('07-OCT-2006',2810,2);
INSERT INTO DEMO_DATA VALUES ('29-AUG-2006',2074,5);


Here is the table and values for the pivot problem

 

 


Followup   October 31, 2006 - 8pm Central time zone:

go for it, if you have access to "expert one on one oracle" I actually provide in there the stored 
procedure that

a) queries a table to find the columns
b) writes a query based on a)
c) opens it

but you get the concept - you know how to pivot, you just need to dynamically figure out the 
columns... 

2 stars pivot table oracle   October 31, 2006 - 8pm Central time zone
Reviewer: prince from CA
I  did something like this:create or replace package my_pkg
as
type refcursor is ref cursor;
type array is table of varchar2(30); 
procedure pivot( p_max_cols       in number   default NULL,
                   p_max_cols_query in varchar2 default NULL,
                   p_query          in varchar2,
                   p_anchor         in array,
                    p_pivot          in array,
                    p_cursor in out refcursor );
 end;
/
create or replace package body my_pkg
  as
 procedure pivot( p_max_cols          in number   default NULL,
                   p_max_cols_query in varchar2 default NULL,
                  p_query          in varchar2,
                  p_anchor         in array,
                   p_pivot          in array,
                   p_cursor in out refcursor )
 as
      l_max_cols number;
      l_query    long;
      l_cnames   array;
  begin
      -- figure out the number of columns we must support
      -- we either KNOW this or we have a query that can tell us
      if ( p_max_cols is not null )
      then
          l_max_cols := p_max_cols;
      elsif ( p_max_cols_query is not null )
      then
          execute immediate p_max_cols_query into l_max_cols;
      else
          raise_application_error(-20001, 'Cannot figure out max cols');
      end if;

      -- Now, construct the query that can answer the question for us...
      -- start with the C1, C2, ... CX columns:
      l_query := 'select ';
      for i in 1 .. p_anchor.count
      loop
          l_query := l_query || p_anchor(i) || ',';
      end loop;
      -- Now add in the C{x+1}... CN columns to be pivoted:
      -- the format is "max(decode(rn,1,C{X+1},null)) cx+1_1"
      for i in 1 .. l_max_cols
      loop
         for j in 1 .. p_pivot.count
          loop
              l_query := l_query ||
                  'max(decode(rn,'||i||','||
                             p_pivot(j)||',null)) ' ||
                             p_pivot(j) || '_' || i || ',';
         end loop;
      end loop;
      -- Now just add in the original query
      l_query := rtrim(l_query,',')||' from ( '||p_query||') group by ';
      -- and then the group by columns...
      for i in 1 .. p_anchor.count
      loop
          l_query := l_query || p_anchor(i) || ',';
      end loop;
      l_query := rtrim(l_query,',');
      -- and return it
      execute immediate 'alter session set cursor_sharing=force';
      open p_cursor for l_query;
      execute immediate 'alter session set cursor_sharing=exact';
  end;
  end;
  /
=========================================================================
variable x refcursor
set autoprint on

begin
     my_pkg.pivot
     ( p_max_cols_query => 'select count (distinct (CODE)) from demo _data',
      p_query => 'select DATE_RT, code, sum(QTY) SUMQTY, row_number()  over ( partition by DATE_RT 
order by CODE) rn from  demo_data group by DATE_RT, ',
       p_anchor => my_pkg.array( 'DATE_RT' ),
       p_pivot  => my_pkg.array( 'CODE', 'SUMQTY'),
        p_cursor => :x );
  end;
 but i am not getting the desire result.

 


Followup   November 1, 2006 - 8am Central time zone:

"sorry"?  I mean - what else can we say?

why are you not getting the desired result?  If you state that, you might be able to figure out how 
to debug your code? 

2 stars pivot table oracle   October 31, 2006 - 8pm Central time zone
Reviewer: prince from CA
 but i am not getting the desire result.

I need to get something 

DATE         1444 2074 2372 2373 2810 2924 2966 3270 3426 
07-OCT-2006  1.02       .05      2.01 4.90  
29-AUG-2006   .01 5.1  .005  
09-OCT-2006                                     .055
26-JUL-2006   .02   1  .05        .02      .025 .015  7 
.
.
.
.

something like this.

so my pivot col is code  and i need the sum of qty displayed like on the top.

Thanks
 


Followup   November 1, 2006 - 8am Central time zone:

so fix your code?  it is what you do for a living right?  develop code to specification.  You have 
the specs, you need to debug your code. 

4 stars If you like SQL :)   November 1, 2006 - 3am Central time zone
Reviewer: Michel Cadot from France
SQL> def colWidth=7
SQL> def f1=999990
SQL> def f2=90D000
SQL> col nop noprint
SQL> set heading off
SQL> with 
  2    cols as (
  3      select to_char(code,'&f1') code, rownum rn, count(*) over () nb
  4      from (select distinct code from demo_data)
  5    ),
  6    dates as ( select distinct date_rt from demo_data ),
  7    data as (
  8      select date_rt, to_char(code,'&f1') code, sum(qty) qty
  9      from demo_data
 10      group by date_rt, code
 11    )
 12  select to_date('01/01/0001','DD/MM/YYYY') nop,
 13         'DATE       '||replace(sys_connect_by_path(code,'/'),'/',' ')
 14  from cols
 15  where level = nb
 16  connect by prior rn = rn-1
 17  start with rn = 1
 18  union all
 19  select to_date('02/01/0001','DD/MM/YYYY') nop,
 20         '-----------'||sys_connect_by_path(lpad('-',&colWidth,'-'),' ')
 21  from cols
 22  where level = nb
 23  connect by prior rn = rn-1
 24  start with rn = 1
 25  union all
 26  select c.date_rt nop, 
 27         c.date_rt||
 28         replace(sys_connect_by_path(nvl(to_char(d.qty,'&f2'),rpad(' ',&colWidth)),'/'),'/',' ') 
    
 29  from ( select code, rn, nb, date_rt from cols, dates ) c,
 30       data d
 31  where d.code (+) = c.code
 32    and d.date_rt (+) = c.date_rt
 33    and level = c.nb
 34  connect by prior c.rn = c.rn-1 and prior c.date_rt = c.date_rt
 35  start with c.rn = 1
 36  order by nop
 37  /
DATE           1444    2074    2372    2810    2924    3270    3372    3426
----------- ------- ------- ------- ------- ------- ------- ------- -------
26-JUL-2006   0.020   2.000   0.100   0.020           0.030           7.000
29-AUG-2006   0.010   5.100   0.005
27-SEP-2006   0.010                           9.450
07-OCT-2006   1.020                   2.010   4.900           0.050   8.000
09-OCT-2006                                           0.055

(I remove code 2966 data to prevent from line fold in report but this does not change anything.)
(This query can be compacted to avoid some repetitions but it is difficult enough to read like 
this.)

Michel
 


4 stars stuck with slightly differen scenario ..need help   November 14, 2006 - 11pm Central time zone
Reviewer: Umesh from USA
Hi Tom , 

as always , this post has been very informational ,especially the use of Analytical function . 

However , i have slightly different need for pivot table 

my scenario 

i have a table 

CREATE TABLE PRICETYPE_PRICELIST
(
  PRICE_TYPE  VARCHAR2(100 BYTE),
  PRICE       NUMBER(15,2),
  ITEM_ID     VARCHAR2(40 BYTE)
)

INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'WSH', 10, '1'); 
INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'WSH', 5, '2'); 
INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'DI', 10, '1'); 
INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'DI', 20, '2'); 
INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'PPD', 2, '1'); 
INSERT INTO PRICETYPE_PRICELIST ( PRICE_TYPE, PRICE, ITEM_ID ) VALUES ( 
'PPD', 4, '2'); 
COMMIT;


basically the Price type values need to be dynamic . 
For now i have only inserted 3 different type valeus .. but the list can group based on future 
requirements

now i have a requirment where in i need this data in following format 

Item Id    WSH  DI  PPD (add on cols )->X      Y     Z ...  
1          10   20   2      
2          5    20   4  

i ran following query to get above output

select item_id
,max(decode(price_type,'WSH',price)) wsh
,max(decode(price_type,'DI',price)) DI
,max(decode(price_type,'PPD',price)) PPD
from PriceType_PriceList p1
group by item_id

however , this restricts me from getting the colums dynamically , ie i would end up hard coding 

any suggestions .. help will really be appreciated  


Followup   November 15, 2006 - 7am Central time zone:

reread the page, there is a solution provided (hint: it takes two steps, one to discover the number 
of columns and then we build a dynamic query and then we use that newly built query) 

1 stars to: Umesh   November 15, 2006 - 8am Central time zone
Reviewer: Michel Cadot from France
Maybe I'm wrong but I don't see any difference with "prince" problem apart from table and column 
names.

Michel
 


2 stars with slight difference..   November 15, 2006 - 12pm Central time zone
Reviewer: Umesh from CA
Michael , 

u r right as for as the output format is concernd ,however , if you look at the contents or the 
values ..my query doesnt run any aggregating functions . All i want is data which is based on 
Price_type stored in rows , in columns format . 

And the problem i am into is that i cant be sure of how many different types of Price Types ,which 
translates to having flexibility of n # of cols based on Price Type . 

hope this explains ..

Said that ..I am still looking for scalable solution :(

Tom ?

will appreciate it .
 


1 stars to: Umesh   November 15, 2006 - 2pm Central time zone
Reviewer: Michel Cadot from France
Try:
%s/demo_data/PRICETYPE_PRICELIST/
%s/date_rt/ITEM_ID/g
%s/code/PRICE_TYPE/g
%s/qty/PRICE/g
%s:to_date('01/01/0001','DD/MM/YYYY'):-2:
%s:to_date('02/01/0001','DD/MM/YYYY'):-1:
and if you don't need to sum then don't sum and group by.

And if you don't want a single query then apply Tom's algorithm.

Michel
 


4 stars Follow-up Question   January 2, 2007 - 10pm Central time zone
Reviewer: Juan from Caracas, Venezuela
Tom, on April 21, 2004 you provided the following query solution:

select rn day, a + rn - 1 dt
from (select a, b from t where id = 1),
    (select rownum rn
      from all_objects
      where rownum <= (select ceil(b - a) + 1 from t where id = 1))


What is I needed to run the query for IDs 1 through 1500? Is there a way to rewrite this query for a range of IDs, each ID being unique in the table?

Thanks.

Followup   January 5, 2007 - 7am Central time zone:

we can do that, and if you prepare a small create table, insert into with just one or two values for ID and different date ranges - I'll show you how.
4 stars Follow-up Question   January 5, 2007 - 6am Central time zone
Reviewer: Juan from Caracas, Venezuela
Hi Tom.

I'd appreciate any feedback on the above question.

Thanks.

4 stars Follow-up Question   January 5, 2007 - 9am Central time zone
Reviewer: Juan from Caracas, Venezuela
Here is the information you requested:

create table t
( id number,
start_datetm date,
end_datetm date)
/

insert into t values (1, '01-JAN-2006', '17-JAN-2006');
insert into t values (2, '02-FEB-2006', '11-FEB-2007');
insert into t values (3, '01-FEB-2005', '10-FEB-2005');
insert into t values (4, '01-JUL-2006', '12-JUL-2006');
insert into t values (5, '01-JAN-2007', '5-JAN-2007');
insert into t values (6, '01-JAN-2006', '7-JAN-2006');

I am trying to the following query do the same for a range of ids.

select id, rn day, start_datetm + rn - 1 dt
from (select id, start_datetm, end_datetm from t where id = 1),
  (select rownum rn
    from all_objects
    where rownum <= (select ceil(end_datetm - start_datetm) + 1 from t where id = 1))
/


Followup   January 5, 2007 - 10am Central time zone:

variable x number
variable y number
exec :x := 1; :y := 10
with
max_spread
as
(select max(ceil(end_datetm - start_datetm) + 1)  spread
   from t
  where id between :x and :y),
data
as
(select level l
   from dual
connect by level <= (select spread from max_spread))
select t.id, data.l day, t.start_datetm+data.l-1 dt
  from t, data
 where l <= ceil(end_datetm - start_datetm) + 1
 order by id, l
/

5 stars Thanks for the query   January 5, 2007 - 10am Central time zone
Reviewer: Juan from Caracas, Venezuela


4 stars Slight clarification   January 6, 2007 - 9am Central time zone
Reviewer: Vinay Pai from London
Hi Tom,

Would this fragment need to be changed from:

where l <= ceil(end_datetm - start_datetm) + 1
order by id, l

to:

where l <= ceil(end_datetm - start_datetm) + 1
and id between :x and :y
order by id, l

Perhaps its not evident in this example because the bind values are for the complete range rather than a subset ?

Anyways, quite a wonderful example of using the with clause !.

Thanks
Vinay


Followup   January 7, 2007 - 7pm Central time zone:

yes indeed, i missed that predicate in the second select on T, thanks!
4 stars Pivot on this requirement   January 9, 2007 - 1am Central time zone
Reviewer: Arindam Mukherjee from Kolkata, India

create table daily_sales
( daily      date,
amount      number,
net_profit  number,
gross_profit  number
)
/

insert into daily_sales values (sysdate-4,100,20,30)
/
insert into daily_sales values (sysdate-3,110,21,31)
/
insert into daily_sales values (sysdate-2,120,22,32)
/
insert into daily_sales values (sysdate-1,130,23,33)
/
insert into daily_sales values (sysdate,140,24,34)
/

SQL> select * from daily_sales;

DAILY      AMOUNT NET_PROFIT GROSS_PROFIT
--------- ---------- ---------- ------------
06-JAN-07    110      21      31
07-JAN-07    120      22      32
08-JAN-07    130      23      33
09-JAN-07    140      24      34


Now I want an output like below.

    DAILY ->  06-JAN-07 07-JAN-07 08-JAN-07 09-JAN-07

Amount          110    120      130  140
NET_PROFIT        21    22      23  24
GROSS_PROFIT        31    32      33  34

I could not find any way to write SQL to get this output. My database is Oracle 10g

Please help me.


Followup   January 11, 2007 - 9am Central time zone:

we need to make amount, net_profit, gross_profit "cross record" first:

ops$tkyte%ORA10GR2> with data
  2  as
  3  (select level l
  4     from dual
  5  connect by level <= 3)
  6  select daily,
  7         decode(l,1,'Amount',2,'Net_Profit',3,'Gross_Profit'),
  8         decode(l,1, Amount ,2, Net_Profit ,3, Gross_Profit )
  9    from daily_sales, data
 10  /

DAILY     DECODE(L,1,' DECODE(L,1,AMOUNT,2,NET_PROFIT,3,GROSS_PROFIT)
--------- ------------ ----------------------------------------------
07-JAN-07 Amount                                                  100
08-JAN-07 Amount                                                  110
09-JAN-07 Amount                                                  120
10-JAN-07 Amount                                                  130
11-JAN-07 Amount                                                  140
07-JAN-07 Net_Profit                                               20
08-JAN-07 Net_Profit                                               21
09-JAN-07 Net_Profit                                               22
10-JAN-07 Net_Profit                                               23
11-JAN-07 Net_Profit                                               24
07-JAN-07 Gross_Profit                                             30
08-JAN-07 Gross_Profit                                             31
09-JAN-07 Gross_Profit                                             32
10-JAN-07 Gross_Profit                                             33
11-JAN-07 Gross_Profit                                             34

15 rows selected.


and then pivoting is straightforward:

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> with data
  2  as
  3  (select level l
  4     from dual
  5  connect by level <= 3)
  6  select tag,
  7         sum( decode( trunc(daily), trunc(sysdate-3), val ) ) "3 days ago",
  8         sum( decode( trunc(daily), trunc(sysdate-2), val ) ) "2 days ago",
  9         sum( decode( trunc(daily), trunc(sysdate-1), val ) ) "1 day ago",
 10         sum( decode( trunc(daily), trunc(sysdate), val ) ) "today"
 11    from (
 12  select daily,
 13         decode(l,1,'Amount',2,'Net_Profit',3,'Gross_Profit') tag,
 14         decode(l,1, Amount ,2, Net_Profit ,3, Gross_Profit ) val
 15    from daily_sales, data
 16         )
 17   group by tag
 18   order by tag
 19  /

TAG          3 days ago 2 days ago  1 day ago      today
------------ ---------- ---------- ---------- ----------
Amount              110        120        130        140
Gross_Profit         31         32         33         34
Net_Profit           21         22         23         24

5 stars Excellent!! Really sterling skill in writing SQL   January 12, 2007 - 8am Central time zone
Reviewer: Arindam Mukherjee from Kolkata, India


3 stars TRANSPOSE function for date datatype or fixed length datat   January 23, 2007 - 3pm Central time zone
Reviewer: Senthil from DENVER, CO USA
TRANSPOSE function for date datatype or fixed length data

select decode(zx.dt, zx.dt, decode(rownum, 1, substr(zx.dt, rownum, rownum + 9),
                              substr(zx.dt, (((ROWNUM-1)*10)+ROWNUM),10)),
                        null) val
from (select '01/01/2006,01/31/2006,08/01/2006,08/10/2006,08/11/2006,01/01/2007' dt
      from dual
    group by cube(1,2,3)
    ) zx
where rownum <= 6

5 stars Is there a dynamic transpose function from row to column   January 26, 2007 - 5pm Central time zone
Reviewer: Biju C from NY
Tom

Did you ever get a chance to write a dynamic transpose function from row to column? Or is it something impossible

I have lot of reports which has to be transposed (convert from row to column) based on one of the dimensions (say Product). I know the product category is fixed and want reports with that as Column Headings.

Thanks for your help as always

4 stars Pivot on this requirement   January 27, 2007 - 12am Central time zone
Reviewer: Alf 
Hi Tom,

The objective of the statement below is to return rows with the values column separated and to trim out the coma character out.

This is what I have so far....

SELECT Y_MUTATIONS.ORDER_ID,
    CLARITY_COMPONENT.NAME,
    substr(ORDER_RES_COMMENT.RESULTS_CMT ||',',1,
    instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',')-1) ORDER_RESULT2,
 
    substr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',',') +1,
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',',', 1, 2) -
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',',')-1) ORD_RS3,

    substr(ORDER_RES_COMMENT.RESULTS_CMT ||',',
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',',1,2)+1,
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',', 1, 2) -
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',')-1) ORD_RS4,

    substr(ORDER_RES_COMMENT.RESULTS_CMT ||',',
    instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',',2,3)+1,
    instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',', 1, 2) -
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',')-1) ORD_RS5,

    substr(ORDER_RES_COMMENT.RESULTS_CMT ||',',
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',',3,4)+1,
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',', 1, 2) -
  instr(ORDER_RES_COMMENT.RESULTS_CMT ||',',',')-1) ORD_RS6,

rtrim(substr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',
    instr(ORDER_RES_COMMENT.RESULTS_CMT ||',,',',',1,2)+1),',') Full_instr_of_values

FROM ("CLARITY"."Y_MUTATIONS" "Y_MUTATIONS" INNER JOIN
    "CLARITY"."CLARITY_COMPONENT" "CLARITY_COMPONENT" ON
    "Y_MUTATIONS"."COMPONENT_ID"="CLARITY_COMPONENT"."COMPONENT_ID")
LEFT OUTER JOIN "CLARITY"."ORDER_RES_COMMENT" "ORDER_RES_COMMENT" ON
    "Y_MUTATIONS"."ORDER_ID"="ORDER_RES_COMMENT"."ORDER_ID"
WHERE "Y_MUTATIONS"."ORDER_ID"=1435844 AND
    ORDER_RES_COMMENT.Line >= Y_MUTATIONS.RESULT_VAL_START_L and
    ORDER_RES_COMMENT.Line <=Y_MUTATIONS.RESULT_VAL_END_LN and
    Y_MUTATIONS.COMPONENT_ID ='6017' and
    "Y_MUTATIONS"."RESULT_VAL_START_L" IS NOT NULL

Ord_ID Result_name  Value1  Value2 value3 value4 value5 Full_instr_of_values
------- ------------  -----  ------ -----  -----  -----  ---------------------------------------------------------
1435844  RT MUTATIONS  M41L  K46K/R  V60I,    D67N,    K70R,    V60I, D67N, K70R, Q102K, K122E, C162S, Y188L, G196E,
1435844  RT MUTATIONS  L210W  R211K  T215Y  K219E  L228H  T215Y, K219E, L228H, V245K, A272P, R277K, K281K/R, V293I,
1435844  RT MUTATIONS  E297K           

The problem with this is that the Full_instr_of_values column doesn't have a fixed numbers of values nor the numbers of commas
so the way I've done so far is not really efficient.

Would please guide me how to wrap this in inside a select case statement?
How would I group this as?

Ord_ID Result_Name    Values
------- ------------    ------
1435844  RT MUTATIONS    M41L
                K46K/R
              V60I,
                D67N,
              ...
                ...
Ord_ID Result_Name    Values
------- ------------    ------
1435844  RT MUTATIONS    L210W
              R211K 
                T215Y 
                K219E 
                L228H 
                ...
                ...
Ord_ID Result_Name    Values
------- ------------    ------
1435844  RT MUTATIONS    E297K

5 stars If I can take my question back   February 5, 2007 - 11am Central time zone
Reviewer: Biju C from New York, NY
If I can take my question back, I would take it out. The answer to my question was right above my question. Just didn't read it properly.

5 stars Student Data   February 19, 2007 - 9am Central time zone
Reviewer: Tony Cillo from San Antonio, TX
Great info!

I am trying to follow your example you gave godwin on june, 20 2003. I have a simular problem. We have students that can have up to 8 courses and I would like to pivot the data so that we have one row for each student. When I run the following query most of the columns are NULL and the ones that have data list the courses more than once.


select StudentID, 
       max(decode(rownum,1,Course_number)) c1,
       max(decode(rownum,2,Course_number)) c2,
       max(decode(rownum,3,Course_number)) c3,
       max(decode(rownum,4,Course_number)) c4,
       max(decode(rownum,5,Course_number)) c5,
       max(decode(rownum,6,Course_number)) c6,
       max(decode(rownum,7,Course_number)) c7,
       max(decode(rownum,8,Course_number)) c8
  from ( select StudentID, Course_number, 
                row_number() over (partition by studentid order by course_number ) 
           from MF_ST_ATTENDANCE )
 group by studentID



Thanks for all the help!

Followup   February 19, 2007 - 9am Central time zone:

do not use rownum - alias the row_number() column to be "RN" or something and use that.
5 stars Thanks!   February 20, 2007 - 7am Central time zone
Reviewer: Tony C from San Antonio, TX
That worked like a charm.

Thanks for all the help and all your hard work Tom!!!

4 stars How do you reference the output   February 22, 2007 - 12am Central time zone
Reviewer: Scott from Perth, Western Australia
G'day Tom,

Back to the original response, where you use cast multiset to get the output:

    DEPTNO X(JOB, CNT)
---------- ----------------------------------------
        10 MYARRAYTYPE(MYSCALARTYPE('CLERK', 1),
           MYSCALARTYPE('MANAGER', 1),
           MYSCALARTYPE('PRESIDENT', 1))


How do we reference this output so it's "useable"?

ie - what sort of dot notation or function do we use to get the literal 'CLERK' and count of "1", instead of seeing output that includes the type name.

Or is it only relevant in an application, would we be selecting this into a type, ready for use?
eg:
v_array myArrayType;
...
dbms_output.put_line(v_array(1).job);


Make sense?

Followup   February 22, 2007 - 8am Central time zone:

you have an array, sqlplus is going to format it as such.

Unless you unnest it (turn it back into rows and columns - but wait, we just took rows and columns and nested them into a collection!!!!)

sqlplus is just a generic data dumping tool - in a "real application" there would be no type names - unless and until you yourself printed them.
4 stars model clause   February 23, 2007 - 6pm Central time zone
Reviewer: Adam from st. louis, MO
if you have the luxury of 10g.

select job
    ,dept_10
    ,dept_20
    ,dept_30
from  emp
model 
return updated rows
partition by ( job )
dimension by ( deptno )
measures (cnt, 0 dept_10, 0 dept_20, 0 dept_30 )
rules
(
  dept_10[0] = cnt[deptno=10]
  ,dept_20[0] = cnt[deptno=20]
  ,dept_30[0] = cnt[deptno=30]
)
order by job
/




4 stars Another Variation of Your Pivot Example   April 18, 2007 - 2pm Central time zone
Reviewer: Fred from Irvine, CA
Hi Tom,

In your first thread above, you offered this suggestion:

scott@DEV816> select job,
2      max( decode( deptno, 10, cnt, null ) ) dept_10,
3      max( decode( deptno, 20, cnt, null ) ) dept_20,
4      max( decode( deptno, 30, cnt, null ) ) dept_30,
5      max( decode( deptno, 40, cnt, null ) ) dept_40
6  from ( select job, deptno, count(*) cnt
7        from emp
8        group by job, deptno )
9  group by job
10 /


I have seen others come up with a similar statement:

select job,
    sum( decode( deptno, 10, 1, 0 ) ) dept_10,
    sum( decode( deptno, 20, 1, 0 ) ) dept_20,
    sum( decode( deptno, 30, 1, 0 ) ) dept_30,
    sum( decode( deptno, 40, 1, 0 ) ) dept_40
from emp
group by job
/

Is there any performance difference between your example and this example? Take for instance if we are talking about a lot of data.



Followup   April 18, 2007 - 2pm Central time zone:

for this, probably "six one way, half dozen the other" - would depend on the number of columns to be generated. 
5 stars SQL Query to do flattening of a joined column   April 20, 2007 - 11pm Central time zone
Reviewer: A reader 
Hi Tom

I have a SQL Qn

-- one row per company
creare table company
(com_id varchar2(10),
stock_symbol varchar2(10),
...
exec_name varchar2(100))

-- contains various names for the company
creare table company_alias
(com_id varchar2(10),
alias varchar2(100))


user types in the name with wildcard on the web-page - we have to match it to the Alias and give One record per company with all the matching aliases - something like

com_id stock_symbol  Exec_name  Aliases
1    ABCD      John Doe  FORD, Ford Motor,X FORD
2    XYZ        Jane Doe  IBM, IBM INDIA, IBM BLU

etc

The Aliases are flattened out and only the Aliases that metch the search have to be included, so For com_id 1 there may be 20 aliases - but if teh search is on the word FORD and only 3 alises match (FORD, Ford Motor,X FORD) then only those should be in the Aliases Column.

The result set needs to paginated for the web application.

Will greatly appreciate some pointers.








Followup   April 21, 2007 - 9am Central time zone:

search this site for

stragg

to pivot and

http://www.oracle.com/technology/oramag/oracle/07-jan/o17asktom.html


for pagination
4 stars Dynamic Transpose Listing   May 3, 2007 - 3am Central time zone
Reviewer: Naveen from India
Table1: (15 columns) 
-------- 
REC_ID  DESC     MODIFIED_BY     MODIFY_DT
101     XYZ     hspi2         12-Aug-06
101     XYZ1     hspi6         14-Jan-07
101     XY Z     hspi12         12-Feb-07

Output Reqd:
REC_ID DESC1     DESC2     DESC3    DESC4 .........DESC{n}
101    XYZ    XYZ1    XY Z


Here {n} is dynamically determined by the number of changes made to DESC columns

Using Oracle 8.1.7, so (With {alias} as ) clause will not work here.


Followup   May 3, 2007 - 5pm Central time zone:

not going to happen "dynamically" since the number of columns sort of (not sort of, does) has to be known at query parse time.
5 stars Finding Relationship   July 30, 2007 - 8am Central time zone
Reviewer: sibghat from Chennai, INDIA
Hi,

A Table contains two columns, column1 and column2 respectively, where these columns represent some 
relationship which shows a > c , b > d, a >d, c >d. basically column1 > column2

 column1 column2
 a       c
 d       b
 a       d
 c       d



I want the output to be greatest,next greatest, next next greatest like (n,n-1,n-2 etc) like

a -> c -> d - > b



It would be of great help if you could help in doing this.
Thanks in advance
Regards,
Sibghat


Followup   July 30, 2007 - 5pm Central time zone:

simple connect by, lots of examples.

I'd have given you one with this data, but no create, no inserts, no look
5 stars CHANGE ROWS TO COLUMN NAMES   July 31, 2007 - 8am Central time zone
Reviewer: writeraj from SINGAPORE
Hi Tom,

is there any way to transpose the data in a row to field name in a query ?
i.e suppose I have the following

Select * from Table1
field_name
--------------
NAME
TITLE

select * from table2
NAME                   TITLE
------------          ------------
PITER                   MR
KNOCK                   MR
BRAFMAN                 MJ

I WANT THE field value in querying TABLE2 only if the column name is listed in table1, otherwise, 
it should return null. Is there anyway to achieve this ?? your quick response is much appreciated.



Followup   August 2, 2007 - 9am Central time zone:

you would have to

a) read the values from table1
b) construct a sql query dynamically
c) then execute it

that is the only way.
3 stars   August 1, 2007 - 4pm Central time zone
Reviewer: Bermuxa from NJ
Hi Writeraj,

SELECT 
  (select t2.name  
   from table1 t1
   where t1.field_name='NAME') as name,
   
  (select t2.title 
   from table1 t1
   where t1.field_name='TITLE') as title
FROM table2 t2
/

        NAME                 TITLE        
-------------------- -------------------- 
PITER                MR                   
KNOCK                MR                   
BRAFMAN              MJ                   

3 row(s) retrieved


delete from table1 where field_name='NAME'
/

1 row(s) deleted

SELECT 
  (select t2.name  
   from table1 t1
   where t1.field_name='NAME') as name,
   
  (select t2.title 
   from table1 t1
   where t1.field_name='TITLE') as title
FROM table2 t2
/

        NAME                 TITLE        
-------------------- -------------------- 
                     MR                   
                     MR                   
                     MJ                   

3 row(s) retrieved


rollback
/

delete from table1 where field_name='TITLE'
/

1 row(s) deleted

SELECT 
  (select t2.name  
   from table1 t1
   where t1.field_name='NAME') as name,
   
  (select t2.title 
   from table1 t1
   where t1.field_name='TITLE') as title
FROM table2 t2
/

        NAME                 TITLE        
-------------------- -------------------- 
PITER                                     
KNOCK                                     
BRAFMAN                                   

3 row(s) retrieved


I hope it helps

3 stars Why dynamic SQL?   August 3, 2007 - 7pm Central time zone
Reviewer: Bermuxa from NJ
Hello Tom,

Your answer to previous question said :
b) construct a sql query dynamically

My question is: why do we need to construct dynamic sql?
Columns of requested query is known at the moment of writing the query and are same as Table2 table columns.
Only content of column is evaluated at runtime. Witch either is the same as in Table2 or is null if name of this column is not in Table1's field_name column.

As Writeraj's question said:
I want the field value in querying TABLE2 only if the column name is listed in Table1, otherwise,
it should return null.


Followup   August 5, 2007 - 1pm Central time zone:

because you don't know

a) how many columns
b) their datatypes
c) their names

until runtime

a SQL query always

a) knows how many columns
b) and their types
c) and their names


You do not have a sql statement until you have a, b, and c.

So, you have to

1) query table2 to get their names
2) build a query that references them by name
3 stars   August 13, 2007 - 2pm Central time zone
Reviewer: A reader from NJ
Tom,
But in this case we know number of columns in query - 2.
Types varchars and names are "NAME" and "TITLE".


Followup   August 15, 2007 - 11am Central time zone:

no, you said they are in a table, anything can be in that table.

obviously, if you KNOW THEIR NAMES, you can code the query, you are wanting to do things "from a table", you don't know their names and their names can change.
3 stars Title of the book   August 13, 2007 - 9pm Central time zone
Reviewer: Tilak from London, UK
hi
Can i know the title of the book where pivoting was discussed by Tom, pl help


Followup   August 20, 2007 - 10am Central time zone:

well, pretty much anything I would have to say about it is actually above. it is a very short topic
5 stars Expert One-On-One Oracle   August 19, 2007 - 11pm Central time zone
Reviewer: Harrison Picot from Haymarket "Home of BMW Motorad uber alles!" Virginia
The first book page 576.  When Tom does not say which book, it is usually the first.


3 stars And 11g time now :)   August 20, 2007 - 10am Central time zone
Reviewer: TongucY from Istanbul, Türkiye
http://www.psoug.org/reference/ora_operators.html#oppv


3 stars Fernando   October 12, 2007 - 1pm Central time zone
Reviewer: Fernando from Peru
Tengo en una tabla estos datos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Y necesito mostrar de esta manera
1 6 11
2 7 12
3 8 13
4 9 14
5 10 15



5 stars Pivoting Millions of Rows - Dynamically   October 25, 2007 - 4pm Central time zone
Reviewer: A reader from USA
We have created a schema design that has been often derided as being impractical, and have made it 
practical, useful, and throught he power of pivots, have made it very efficient and fast.
We are using a cousin of what has been called EAV or Value Pair.  We have all of our data values 
stored vertically.  Each has an element name stored as an ID.  Each ID is stored with a set which 
contains all of the necessary meta-data.  We also store column sequence.
With those intact, we are able to write a single query, stored in a proc, that will never need to 
be changed since it will pick up any new columns in the form of a row in the EAV-like table.
Once the analyze is run (less than 1 second), we are able to dynamically pivot the query based on 
the Max(decode... and the column sequence that is added on new column insert.
An analyze is run after each insert of a virtual row, which can be hundreds of physical rows.
As it stands we have over 3 million phycial rows of data, an number of which can be pulled in < 1 
second, pivoted and returned to the user as normal, horizontal data.  Thank you for your 
explanations of pivoting.  And, although I know many dislike the idea of the table design 
described, we are not only using it efficiently, but we are able to perform operations on tables 
that we normally would not be able to.


Followup   October 25, 2007 - 6pm Central time zone:

example please - your text description does not do this justice (since I didn't follow it)
4 stars Pivoting a Big Table   October 27, 2007 - 12pm Central time zone
Reviewer: Dav from USA
Hi, 
We've Oracle Financial application running on 9.2.8 version databae. 

There is a table called GL_BALANCES which has balance information for each 
code_combination_id,currency_code, set_of_books_id, period. The structure looks like something like 
this. 

CODE_COMBINATION_ID NUMBER       ( for ex. 1201212)
PERIOD_NAME         VARCHAR2(30) ( for ex. SEP-07)
PERIOD_YEAR         NUMBER       ( for ex. 2007)
CURRENCY_CODE       VARCHAR2(30) ( for ex. USD or EUR..)
SET_OF_BOOKS_ID     NUMBER       ( for ex. 1001, 1002.. )
ACTUAL_FLAG         VARCHAR2(1)  ( A (actual), B (budget))
PERIOD_NET_CR       number       ( for ex. 100.01)
PERIOD_NET_DR       number       ( for ex. 200.01)
BEGIN_BALANCE_CR    number       ( for ex. 200.00)
BEGIN_BALANCE_DR    number       ( for ex. 400.00)

from this table , we pivot the data in the following format 
code_combination_id,period_year, currency_code,actual_flag, Jan_begin_balance_dr, 
jan_begin_balance_cr,..12 month buckets.. 

We've 7 mil code combinations and depends on the calendar month we need to fill the monthly bucket 
from Jan to current period. So when we run it this month Oct.07 , We need to look up 70 million 
records( 7 million records/per month for 10 months) and do the pivot. The query takes too long. 
Even though the data may be changing for current period and previous period ( oct.07 and sep.07), 
we still need to fill up buckets from Jan to Oct. 

I tried Materalized view ., it's not accepting FAST REFRESH mode, because of complex query. ( pivot 
query has max(decode..) clause. 

Any Idea to make this process faster?

Thank you in advance.





 


5 stars Pivoting Millions of Rows - Dynamically -follow up   October 30, 2007 - 1pm Central time zone
Reviewer: A reader from USA
<code>I apologize if these two tables to not give the big picture. The total solution is too large to fully paste. However,the data_string_txt is where the data is stored. it is pivoted based on the col_pos_seq in the xx_tmplt table. The pivot is built dynamically by looping through our possible values and building the statement in a clob and executing. As long as we do an analyze after large amounts of data are inserted, we have no problem with scale. we currently pivot data within sets that are > 3 mil.

CREATE TABLE XX_ELMNT
(
LVL1_ID      NUMBER,
ELMNT_ID      NUMBER,
DATA_STRING_TXT VARCHAR2(200 BYTE),
TXN_ID      NUMBER,
SHEET_ID      NUMBER
);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258150, 1, 'ADD', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258151, 126, '400000968', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258152, 454, '0', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258153, 371, 'SB', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258154, 311, 'HYLHG', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258155, 129, '10KM-021852', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258156, 112, '19671024', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258157, 131, 'F', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258158, 304, '7368 RUGBY STREET', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258159, 306, 'PHILADELPHIA', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258160, 309, 'PA', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258161, 310, '191380000', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258162, 446, '521031854', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258163, 122, '20070701', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258164, 450, '0001', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258165, 449, '20070701', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258166, 67, 'A001', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258167, 66, '20070701', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258168, 71, 'MCAP0002', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258169, 70, 'SELECT', 6012, 3);
Insert into XX_ELMNT
  (LVL1_ID, ELMNT_ID, DATA_STRING_TXT, TXN_ID, SHEET_ID)
Values
  (258170, 68, '20060101', 6012, 3);
COMMIT;




CREATE TABLE XX_TMPLT
(
DTL_ID        NUMBER,
TMPLT_ID      NUMBER,
COL_POS_SEQ    NUMBER,
COLTYPE_ID      NUMBER,
SHEET_ID      NUMBER,
COL_HDG_NM      VARCHAR2(50 BYTE),
REQD_IND      VARCHAR2(1 BYTE),
MIN_VALUE_TXT    VARCHAR2(30 BYTE),
MAX_VALUE_TXT    VARCHAR2(30 BYTE),
MIN_FLD_SZ_NBR  NUMBER,
MAX_FLD_SZ_NBR  NUMBER,
DEFAULT_VALUE_TXT VARCHAR2(50 BYTE),
ELMNT_ID      NUMBER,
BULK_CHG_IND    CHAR(1 BYTE),
VAL_PARNT_ID    NUMBER,
TMPLT_NOTES    VARCHAR2(2000 BYTE),
COL_START      NUMBER,
ADDL_EDITS_IND  CHAR(1 BYTE)
)
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (472, 3, 1, 1, 3,
  'ACTION', 'Y', NULL, NULL, 3,
  3, 'ADD', 1, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (473, 3, 2, 1, 3,
  'FAMILY_LINK_ID', 'Y', NULL, NULL, 0,
  0, NULL, 126, 'Y', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (474, 3, 3, 1, 3,
  'SUFFIX', 'Y', NULL, NULL, 1,
  2, NULL, 454, 'Y', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (475, 3, 4, 1, 3,
  'RELATIONSHIP', 'Y', NULL, NULL, 2,
  2, NULL, 371, 'Y', 11,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (476, 3, 5, 1, 3,
  'LAST_NAME', 'Y', NULL, NULL, 1,
  30, NULL, 311, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (477, 3, 6, 1, 3,
  'FIRST_NAME', 'Y', NULL, NULL, 1,
  30, NULL, 129, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (480, 3, 9, 1, 3,
  'DATE_OF_BIRTH', 'Y', NULL, NULL, 8,
  8, NULL, 112, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (481, 3, 10, 1, 3,
  'GENDER', 'Y', NULL, NULL, 1,
  1, NULL, 131, 'N', 2,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (482, 3, 11, 1, 3,
  'HOME_ADDRESS1', 'Y', NULL, NULL, 1,
  40, NULL, 304, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, SHEET_ID,
  COL_HDG_NM, REQD_IND, MIN_VALUE_TXT, MAX_VALUE_TXT, MIN_FLD_SZ_NBR,
  MAX_FLD_SZ_NBR, DEFAULT_VALUE_TXT, ELMNT_ID, BULK_CHG_IND, VAL_PARNT_ID,
  TMPLT_NOTES, COL_START, ADDL_EDITS_IND)
Values
  (484, 3, 13, 1, 3,
  'HOME_CITY', 'Y', NULL, NULL, 1,
  40, NULL, 306, 'N', NULL,
  NULL, NULL, NULL);
Insert into XX_TMPLT
  (DTL_ID, TMPLT_ID, COL_POS_SEQ, COLTYPE_ID, S

Followup   October 30, 2007 - 1pm Central time zone:

... As long as we do an analyze
after large amounts of data are inserted, we have no problem with scale. ...

so, now I don't know what you are asking for?
5 stars Data pivot   October 30, 2007 - 3pm Central time zone
Reviewer: A reader from USA
Im not asking for anything.  Per the intent of "Write a Review" I was providing feedback on 
Pivoting.  And how we have successfully used it for large amounts of data dynamically.

Thank you for the informative discussion and here's an example of pratcial application.  That's all.


2 stars feedback on "providing feedback on Pivoting"   October 31, 2007 - 4pm Central time zone
Reviewer: another reader ... or writer? 
To "A Reader":

an example of pratcial application

It is indeed practical in the sense that it is doable, a concrete application of the technique.

It is otherwise impractical in the sense that it is not sensible, not rational to do something for no reason what-so-ever. Well, you alluded to "we are able to perform operations on tables that we normally would not be able to" but have otherwise provided not one shred of evidence that the cousin of EAV is "practical, useful [snip] efficient and fast". On the fast portion, remember you should be proving faster than not just fast enough.

Your consumer needs a result set equivalent to: "Select A, B, C from T where P"

One could have a table T with columns A, B, C and execute the above SQL by supplying input for the predicate P.

You don't have a table T. You have an EAV model forcing you into pivoting in order the supply the consumer with the result set. You have relatively small amounts of data and pivoting is fast enough. So what? What is the advantage of not having T implemented as a table?

Out of curiosity:
1. Why do you ORDER BY your generated SQL?
2. If your consumer really needs a predicate like "where DATE_OF_BIRTH = :d" (DATE_OF_BIRTH being your DTL_ID=480), when does that happen? Is the consumer pivoting and retrieving the entire, ordered, content of your virtual table T and then applying the predicate?




3 stars Logical Leaps   November 7, 2007 - 9am Central time zone
Reviewer: A reader 
I don't understand how you would know that we did this for no reason what-so-ever.  (we did do it 
for no reason what-so-ever, but how would YOU know that?  We just did it for slaps and giggles and 
to waste company money ;-) )  Seriously, we had a problem to solve and this was the only way to 
solve it.  So does that make you revise your impracticality claim?

I don't really have to prove "faster than" anything because I wasn't writing to show anybody up.  i 
was writing to demonstrate that using pivot with EAV-like schema can be used practically and with 
speed and efficiency.  Also, "faster-than" what?  i'm not sure what you want me to be faster than.

" What is the advantage of not having T implemented as a table? " - The advantage is it solved the 
problem we were given that we needed to be able to add columns to tables without changing code. Do 
you have another way to do that using standard tables?

Out of curiosity: 
1. Why do you ORDER BY your generated SQL? --because the result set needs to be ordered.

2. If your consumer really needs a predicate like "where DATE_OF_BIRTH = :d" -our consumer doesn't 
need a predicate like "where date_of_birth = :d" so the question is moot.  Secondly, if that were 
the case, we have contrived key's that take care of indexing and finding particular data based on 
keys and foreign keys.


4 stars Help, pls..   November 26, 2007 - 4pm Central time zone
Reviewer: Mich from Singapore
Hi, i need help. I have a table named, 'Attendance'
with the following fields and data(prob more),
---------------------------------------
StdID    SubjID   ADate          StdAttend
=======================================
1001     2001     2007-11-19     present
1002     2001     2007-11-19     absent
1003     2001     2007-11-19     present
1001     2001     2007-11-26     absent
1002     2001     2007-11-26     present
1003     2001     2007-11-26     present
1001     2001     2007-12-03     present
1002     2001     2007-11-03     present
1003     2001     2007-11-03     absent
========================================
And would like to create a query/report from it that would look like this:
---------------------------------------------------
StdId     2007-11-19    2007-11-26     2007-12-03
===================================================
1001      present       absent         present
1002      absent        present        present
1003      present       present        absent
===================================================
I found a way of doing this by:

select StdID,
       sum(case when ADate='2007-11-19' then 1 else 0 end) as "2007-11-19",
       sum(case when ADate='2007-11-26' then 1 else 0 end) as "2007-11-26",
       sum(case when ADate='2007-12-03' then 1 else 0 end) as "2007-12-03"
from Attendance
group by StdID, SubjID


But, what if i don't know the exact value of 'ADate' and still want to add it into the ff. columns?


P.S.
Is it also possible to add a last column like this, after all the dates have been listed? Sorry, 
I'm very new to this.
-----------------------------------------------------------
StdId     2007-11-19    2007-11-26     2007-12-03     Total
===========================================================
1001      present       absent         present          2/3
1002      absent        present        present          2/3
1003      present       present        absent           2/3
===========================================================

Thanks a bunch,
Mich


Followup   November 27, 2007 - 3pm Central time zone:

ff columns? what the heck is an ff column.

and if you do not know adate - is adate an INPUT to the query or what? what if there are thousands of adates in the table?

also, next time you hit the review button, please take a second to read the text I put at the top of the page - if you don't, I'll just be ignoring the post altogether - you know the bit that says:

If your followup requires a response that might include a query, you had better supply very very simple create tables and insert statements. I cannot create a table and populate it for each and every question. The SMALLEST create table possible (no tablespaces, no schema names, just like I do in my examples for you)
3 stars PQ on cartesian result set   November 27, 2007 - 2am Central time zone
Reviewer: Marc 
Dear Tom,
is there a chance to have the result set of the cartesian product processed in parallel ?
as soon as the data comes back from a query like :
select *
from tableA, tableB
;
there is only 1 CPU doing the rest and the "rest" easily could be a result set of 500 mio rows 
which I want some
heavy case-when expressions run at.
thanks a lot
regards
Marc


Followup   November 27, 2007 - 3pm Central time zone:

sure, yes, just alter the tables to be parallel (or use parallel hints)
3 stars cartesian product in parallel   November 28, 2007 - 7am Central time zone
Reviewer: Marc 
I hadn't written this if I didn't do so.
tablescans are done in parallel indeed , but the result is processed by 1 CPU as mentioned...
pretty easy to reproduce
10.2.0.3
--------
alter table X parallel 20;
alter table Y parallel 20;

select *
from X,Y
;

launch prstat simultaneously 
wait until data comes back in the sqlplus session
from that moment onwards 1 CPU is working only

the problem is that I got something like

select * from 
(
select case when huge ...expression ..
from T_20_mio_rows , T_200_rows
) 
where shrink_back_restriction_based_upon_huge_case ...
;

and u can imagine that processing the result set of 20 Mio x 200 rows in serial is really slow


Followup   November 28, 2007 - 11pm Central time zone:

20,000,000 x 200 should give me rows back almost instantly in serial - take first row of 20,000,000 and join to first row of 200 - bamm

I'm not sure what you are looking for here from parallel. the case isn't going to be a huge bottleneck, not compared to shipping 4,000,000,000 (4 BILLION) records anyway.
5 stars pivot with model   December 1, 2007 - 10am Central time zone
Reviewer: rc from The Netherlands
Adam from st. louis, MO gave a select statement with model but that doesn't work. 

select job 
    ,dept_10 
    ,dept_20 
    ,dept_30 
from  emp 
model  
return updated rows 
partition by ( job ) 
dimension by ( deptno ) 
measures (cnt, 0 dept_10, 0 dept_20, 0 dept_30 ) 
rules 
( 
   dept_10[0] = cnt[deptno=10] 
  ,dept_20[0] = cnt[deptno=20] 
  ,dept_30[0] = cnt[deptno=30] 
) 
order by job 
/ 

Probably emp is not the emp table but a view?? 

I use this select statement:

select distinct job, deptno_10, deptno_20, deptno_30, deptno_40
from   (select job,deptno,count(*) cnt
        from   emp
        group by job,deptno ) empv
model
partition by (job)
dimension by (deptno)
measures (cnt ,0 deptno_10 ,0 deptno_20 ,0 deptno_30, 0 deptno_40  )
rules
(
  deptno_10[any] = cnt [deptno = 10]
, deptno_20[any] = cnt [deptno = 20]
, deptno_30[any] = cnt [deptno = 30]
, deptno_40[any] = cnt [deptno = 40]
)
order by job
;


JOB        DEPTNO_10  DEPTNO_20  DEPTNO_30  DEPTNO_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

This works because the combination of job and deptno is unique for inline view empv. 

The column deptno_40 is not necessary because there are no employees with deptno = 40. 

select ... model ... works only in Oracle 10 and Oracle 11 . 


4 stars when I run this query, I get an ORA-00932 inconsistent datatypes error ???   December 6, 2007 - 3pm Central time zone
Reviewer: A reader 
When I tried to run Tami's proposed solution for Warren, I get an ORA-00932 inconsistent datatypes 
error. Tom, can you please tell me why I receive and/or how to get around the ORA error.

SQL> get warren.sql
  1  select Y.id,
  2         (case
  3         when y.site_id is not null then y.site_id
  4         else  (select site_id
  5                  from T1
  6                 where T1.id = prev_site_id)
  7         end ) NSITE_ID,
  8         ( case
  9           when y.regional_office is not null then y.regional_office
 10           else ( select regional_office
 11                   from  T1
 12                  where  T1.id = prev_reg_id)
 13         end ) NRegional_office,
 14         y.comm_name
 15  FROM
 16  (select a.id as id , a.site_id as site_id,
 17         (select max(id)
 18           from  (select distinct id, site_id
 19                    from t1 where site_id is not null) b
 20          where  b.id < a.id ) prev_site_id,
 21         regional_office regional_office,
 22         (select max(id)
 23            from (select distinct id , regional_office
 24                    from t1 where regional_office is not null) c
 25           where c.id < a.id ) prev_reg_id
 26  from t1 a ) X,
 27  T1 Y
 28* where x.id = Y.id
SQL> /

        ID   NSITE_ID NREGIONAL_OFF COMM_NAME
---------- ---------- ------------- ------------------------------
         1          9 Region 1      NEW YORK WHARVES LTD
         2          9 Region 1      AE SHIPPERS
         3         10 Region 2      ESSO BULK PLANT
         4         10 Region 2      SHELL BULK PLANT
         5         10 Region 2      STATION A
         6         10 Region 2      STATION B
         7         10 Region 2      STATION C
         8         10 Region 2      STATION D
         9         10 Region 2      STATION E
        10         10 Region 2      STATION F
        11         11 Region 3      RAIL RIGHT-OF-WAY
        12         11 Region 3      STORAGE SHED
        13         11 Region 3      FUELING DE
        14         11 Region 3      EMERGENCY SHELTER
        15         13 Region 4      CANRON

15 rows selected.

Tom, I tried lead/lag function. I am getting previous site for the subsequent row, but for all the 
rows.

I am sure, Tom will come up with "ANALYTIC FUNCTION". 


5 stars replacing dept_10 with corresponding deptno   June 12, 2008 - 5am Central time zone
Reviewer: Bhaskar from India
Hi Tom your o/p is like this 

JOB          DEPT_10    DEPT_20    DEPT_30    DEPT_40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4

but how i can repalce the headings like DEPT_10 with it's corresponding deptno like in a single sql 
query?

JOB               10         20        30         40
--------- ---------- ---------- ---------- ----------
ANALYST                       2
CLERK              1          2          1
MANAGER            1          1          1
PRESIDENT          1
SALESMAN                                 4


Followup   June 12, 2008 - 8am Central time zone:

scott@DEV816> select job,
  2         max( decode( deptno, 10, cnt, null ) ) "10",
  3         max( decode( deptno, 20, cnt, null ) ) "20",
  4         max( decode( deptno, 30, cnt, null ) ) "30",
  5         max( decode( deptno, 40, cnt, null ) ) "40"
  6    from ( select job, deptno, count(*) cnt
  7             from emp
  8            group by job, deptno )
  9   group by job
 10  /

4 stars Row vs Column Comparison   July 7, 2008 - 2pm Central time zone
Reviewer: Radhika from USA
I have two tables like tableA,tableB
The row in tableA is exactly instered into tableB as Columns
For eg: 
tableA
------        
Num Name Sal
=== ==== ===
1   XXX  12000

tableB
-------
Col1
====
1
XXX
12000

Now how to do a comparision between tableA and tableB to make sure whether the values in the source 
(Row) is same as in the targets (column values).


Followup   July 7, 2008 - 3pm Central time zone:

you are missing stuff here. tableB is entirely incomplete in it's design. It is missing at least two columns that would a) comprise the primary key, b) provide a way to order the rows

Actually, just please drop tableB, it is a really bad idea.


oh, and no create, no inserts, no look
4 stars Row Vs Column comparision   July 8, 2008 - 2pm Central time zone
Reviewer: Radhika from USA
The requirement is like that.Need to insert a row in one table as column in another table. The 
target table column is of Varchar type. I want a query to compare the source and the target thats 
row vs column.


Followup   July 8, 2008 - 5pm Central time zone:

what is driving this requirement

because this requirement is a really bad idea

let us backup and get to the crux of the problem "how can we help you defeat this really bad idea and make you look better"
4 stars Unknown column number and name   July 16, 2008 - 10am Central time zone
Reviewer: John from Italy
Hi, I have split my data in 3 tables: LOG, MESSAGEFIELDS, AND LOGMESSAGEDETTAILS. The tables look as follows:
LOG
-----------------------------------------------------------------------------------------------
ID      DATETIME                MESSAGE
-----------------------------------------------------------------------------------------------
1       2008-04-02 11:45:01     attr_01=val_01 attr_02=val_02 <otherdata>
2       2008-04-04 10:12:05     attr_02=val_01 attr_03=val_02 attr_04=val_03
3       2008-04-01 09:01:24     attr_04=val_01 <otherdata>
4       2008-04-11 23:45:17     attr_01=val_01 attr_02=val_02 attr_03=val_03 attr03=val_04
...


MESSAGEFIELDS
----------------
ID      FIELD
----------------
1       attr_01
2       attr_02
3       attr_03
4       attr_04
...


LOGMESSAGEDETTAILS
---------------------------------------
ID    LOGID    FIELDID   FVALUE
---------------------------------------
1     1        1         val_01
1     1        2         val_02
1     2        2         val_01
1     2        3         val_02
1     2        4         val_03
1     3        4         val_01
1     4        1         val_01
1     4        2         val_02
1     4        3         val_03
1     4        4         val_04
...



The LOG.MESSAGE field is composed of valued attributes and other types of data. The valued attributes are programmatically extracted and inserted into a dettails table.

The result I want to obtain is:

DETTAILEDLOGVIEW
---------------------------------------------------------------------------------------------------
ID   DATETIME              genericData    attr_01    attr_02    attr_03    attr_04        ...
---------------------------------------------------------------------------------------------------
1    2008-04-02 11:45:01   <otherdata>    val_01     val_02
2    2008-04-04 10:12:05                             val_01     val_02     val_03
3    2008-04-01 09:01:24   <otherdata>                                     val_01
4    2008-04-11 23:45:17                  val_01     val_02     val_03     val_04
...


Could you give a hint on the best way to do this please?

5 stars pivot   March 2, 2009 - 12am Central time zone
Reviewer: A reader 
Tom:

is this done using a pivot query.
I want to get a 5 column result (one per video) based on the following table/data and queries.
When i do those as subqueries it takes a long time "select a.vidno,(sub1),(sub2),(sub3) from table 
a".
Also, would you consider creating a trigger and storing those values instead of calculating every 
time for better performance.
  
 vidno, date_assign, curr_assigned, prev_asigned, last_assigned
  
  Date Assigned = select max(date_assigned) from assign c where vidno=a.vidno
  
  Current Assigned  = select assigned_to from assign d where (vidno,date_assigned) in
                           (select vidno,max(date_assigned) from assign where vidno=a.vidno
                            and status_code='A' group by vidno)
                and d.date_assigned >= a.recdt
  
  Previous Assigned  = select assigned_to from assign c where (vidno,created_date) in
              (select vidno,max(date_assigned) from assign where vidno=a.vidno
              and status_code='T'  group by vidno)
              
  Last Assigned = select assigned_to from assign t where (vidno,created_date) in
                        (select vidno,max(created_date) from assign where vidno=a.vidno
                      group by vidno


create table assign (
 assign_no   number(10), --PK
 vidno        number(10),
 media       varchar2(3),
 assigned_to   varchar2(10),
 date_assigned  date,
 status_code   varchar2(1),
 created_Date  date)
 
 insert into assign values (1,1001,'CA','mike','01-JAN-2009','A',sysdate)
 /
 insert into assign values (2,1002,'CA','sam','02-JAN-2009','T',sysdate)
 /
 insert into assign values (3,1002,'CA','john','03-JAN-2009','A',sysdate)
 /
 insert into assign values (4,1003,'CA','trish','04-JAN-2009','T',sysdate)
 /
 insert into assign values (5,1003,'CA','suzan','05-JAN-2009','T',sysdate)
 /
 insert into assign values (6,1003,'CA','wiley','06-JAN-2009','A',sysdate)
 /
 insert into assign values (7,1004,'CA','jenn','07-JAN-2009','T',sysdate)
 /
 insert into assign values (8,1004,'CA','wesl','08-JAN-2009','T',sysdate)
 /
 insert into assign values (9,1004,'CA','eric','09-JAN-2009','T',sysdate)
 /
 insert into assign values (10,1004,'CA','magic','10-JAN-2009','A',sysdate)
 /
 insert into assign values (11,1005,'CA','tese','11-JAN-2009','T',sysdate)
 /
 insert into assign values (12,1005,'CA','dera','12-JAN-2009','A',sysdate)
 /
 insert into assign values (13,1005,'CA','simon','13-JAN-2009','T',sysdate)
 /
 insert into assign values (14,1005,'CA','liz','14-JAN-2009','A',sysdate)
 /
 
 
 


Followup   March 3, 2009 - 3pm Central time zone:

... Also, would you consider creating a trigger and storing those values instead of
calculating every time for better performance.
....

absolutely NOT.

It is not at all clear what you want there, instead of giving me snippets of code (that do not work standalone) - how about you either give us a FUNCTIONING query to look at and try to understand what you want or even better - explain it in specification like form.
5 stars pvt query   March 3, 2009 - 8pm Central time zone
Reviewer: A reader 
Tom:

thanks, here is the query and specs for tables/data. This one will run fast. However, the QSTAGES 
table i have has about 200,000 records and ASSIGN table is 1000.

It takes 1.5 minutes for user to see records on screen.
I did troubleshooting and found that the congestions is when the sub-query on ASSIGN table multiple 
time to figure out current and previous reviewer.  The main query where i use RANK() runs pretty 
fast.


SELECT 
"MEDIA","VIDNO","STAGE","STATUS","RECDT","CREATED_DATE","PREV_REVIEWER","CURR_REVIEWER","MINDATE1","
MINDATE2" FROM  (  select g.*,min(mindate1) over () MINDATE2 from  (
  select  e.media,e.vidno,e.stage,e.status,e.recdt,e.created_date,
  (case when recdt > date_assigned then last_assigned else old_prev_reviewer end) prev_reviewer,
  curr_reviewer,
  (case when curr_reviewer is null then recdt else null end) mindate1
  FROM
  (
   select a.media,a.vidno,a.stage,a.status,a.recdt,a.created_date,
   (select assigned_to from assign c where (vidno,date_assigned) in
   (select vidno,max(date_assigned) from assign where vidno=a.vidno and status_code='T' 
    group by vidno) ) old_prev_reviewer,
   (select max(date_assigned) from assign c where vidno =a.vidno) date_assigned,
   (select assigned_to from assign t where (vidno,date_assigned) in
   (select vidno,max(date_assigned) from assign where vidno=a.vidno
   group by vidno) ) last_assigned,
   (select assigned_to from assign d where (vidno,date_assigned) in
   (select vidno,max(date_assigned) from assign where vidno=a.vidno and status_code='A'
   group by vidno)
    and d.date_assigned >= a.recdt) Curr_reviewer
  FROM
(
SELECT * FROM
 (
 select a.*, dense_rank() over (partition by vidno order by seq_no desc) as rnk
   FROM qstages a where status <> 'I')
  WHERE stage = 0 and status = 'A' and rnk = 1 order by recdt
) A
where rownum <= (select to_Number(parameter_value) from ref_sys_parameters where 
lower(parameter_name)='no_rows') order by recdt,vidno
) E
) G
)
where recdt <= nvl(mindate2+(select to_Number(parameter_value) from ref_sys_parameters where 
lower(parameter_name)='assign_days'),recdt)
/


create table ref_sys_parameters (
  parameter_name varchar2(20),
  parameter_value varchar2(15) );
  
insert into ref_sys_parameters values ('assign_days','150')
/
insert into ref_sys_parameters values ('no_rows','100')
/
  

create table qstages (
 seq_no number(10),
 vidno  number(10) ,
 media varchar2(3),
 stage number(1),
 recdt date,
 compdt date,
 status varchar2(1),
 created_Date   date )
/
insert into qstages values (1,1000,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (2,1001,'CA',0,'10-NOV-2008','10-NOV-2008','R',sysdate)
/ 
insert into qstages values (3,1002,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (4,1002,'CA',0,'10-NOV-2008','10-NOV-2008','R',sysdate)
/ 
insert into qstages values (5,1003,'CA',0,'10-NOV-2008','10-NOV-2008','R',sysdate)
/
insert into qstages values (6,1003,'CA',0,'12-NOV-2008','12-NOV-2008','A',sysdate)
/ 
insert into qstages values (7,1004,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (8,1004,'CA',1,'12-NOV-2008','12-NOV-2008','A',sysdate)
/ 
insert into qstages values (9,1005,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (10,1005,'CA',1,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (11,1005,'CA',5,'12-NOV-2008','12-NOV-2008','A',sysdate)
/ 
insert into qstages values (12,1006,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (13,1006,'CA',1,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (14,1006,'CA',5,'10-NOV-2008','10-NOV-2008','R',sysdate)
/ 
insert into qstages values (15,1007,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (16,1007,'CA',1,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (17,1007,'CA',5,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (18,1007,'CA',2,'12-NOV-2008','12-NOV-2008','A',sysdate)
/ 
insert into qstages values (19,1008,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (20,1008,'CA',1,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (21,1008,'CA',5,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (22,1008,'CA',2,'12-NOV-2008','12-NOV-2008','R',sysdate)
/
insert into qstages values (23,1008,'CA',0,'12-NOV-2008','12-NOV-2008','A',sysdate)
/
insert into qstages values (24,1009,'CA',0,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (25,1009,'CA',1,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (26,1009,'CA',5,'10-NOV-2008','10-NOV-2008','A',sysdate)
/
insert into qstages values (27,1009,'CA',2,'12-NOV-2008','12-NOV-2008','R',sysdate)
/
insert into qstages values (28,1009,'CA',0,'12-NOV-2008','12-NOV-2008','R',sysdate)
/


      


Followup   March 4, 2009 - 12pm Central time zone:

oh
my
gosh

Ok, now I hate scalar subqueries. They are nice for very few things. This is NOT one of them.


so, I reformat your query so we have a *chance* of getting it - by the way, when one asks for "specifications",

.... thanks, here is the query and specs for tables/data. ...

that isn't what they are asking for. They are asking for a textual, step by step description of the problem (the QUESTION). You know, that material YOU had when YOU developed this thing in the first place.

and it would have been nice to have, well, all of the tables - don't you think - that assign table seems rather "relevant" no?


I think the ability to phrase a question is the most important attribute one can have in our profession. I see it rarely.


SELECT "MEDIA","VIDNO","STAGE","STATUS","RECDT","CREATED_DATE",
       "PREV_REVIEWER","CURR_RE VIEWER","MINDATE1","MINDATE2"
  FROM (  select g.*,min(mindate1) over () MINDATE2
            from ( select e.media,e.vidno,e.stage,e.status,e.recdt,e.created_date,
                          (case when recdt > date_assigned
                                then last_assigned
                                else old_prev_reviewer
                            end ) prev_reviewer,
                          curr_reviewer,
                          (case when curr_reviewer is null
                                then recdt
                                else null
                            end ) mindate1
                     FROM ( select a.media,a.vidno,a.stage,a.status,a.recdt,a.created_date,
                                   (select assigned_to
                                      from assign c
                                     where (vidno,date_assigned) in
                                           (select vidno,max(date_assigned)
                                              from assign
                                             where vidno=a.vidno
                                               and status_code='T'
                                             group by vidno) ) old_prev_reviewer,
                                     (select max(date_assigned)
                                        from assign c
                                       where vidno =a.vidno) date_assigned,
                                     (select assigned_to
                                        from assign t
                                       where (vidno,date_assigned) in
                                             (select vidno,max(date_assigned)
                                                from assign
                                               where vidno=a.vidno
                                               group by vidno) ) last_assigned,
                                     (select assigned_to
                                        from assign d
                                       where (vidno,date_assigned) in
                                             (select vidno,max(date_assigned)
                                                from assign
                                               where vidno=a.vidno
                                                 and status_code='A'
                                               group by vidno)
                                         and d.date_assigned >= a.recdt) Curr_reviewer
                              FROM ( SELECT *
                                       FROM ( select a.*,
                                                     dense_rank() over
                                                          (partition by vidno
                                                               order by seq_no desc) as rnk
                                                FROM qstages a
                                               where status <> 'I')
                                         WHERE stage = 0
                                           and status = 'A'
                                           and rnk = 1
                                          order by recdt) A
                             where rownum <= (select to_Number(parameter_value)
                                                from ref_sys_parameters
                                               where lower(parameter_name)='no_rows')
                             order by recdt,vidno
                          ) E
                 ) G
       )
 where recdt <= nvl(mindate2+(select to_Number(parameter_value)
                                from ref_sys_parameters
                               where lower(parameter_name)='assign_days'), recdt )





Ok, so you get the set of records for each vidno after sorting by seq_no from big to small. You don't say of seq_no is UNIQUE or anything and since you use dense_rank, that could be more than one record PER vidno (do you see why
specifications are necessary, I'm pretty sure that seq_no is unique - but you don't say - I don't know - I cannot assume so I have to live with the ASSUMPTION that it isn't - I have to live with ALL of the assumptions one can derive from your query - which isn't performing well - which means I am shackled by all of the hidden assumptions. If we had a well formed DATA MODEL (constraints and all) and a well formed QUESTION - "this is the data I need...." sort of question - we'd be much better off...)



but you do it in sort of a strange fashion:

                              FROM ( SELECT *
                                       FROM ( select a.*,
                                                     dense_rank() over
                                                          (partition by vidno
                                                               order by seq_no desc) as rnk
                                                FROM qstages a
                                               where status <> 'I')
                                         WHERE stage = 0
                                           and status = 'A'
                                           and rnk = 1
                                          order by recdt) A



did you really mean to assign rank BEFORE filtering on stage and status? We'll assume so...

Then you keep only rownum <= value of them...

Now, for each of thos vidno's, you want

a) assigned_to for the status_code = 'T' with the max(date_assigned)
b) max(date_assigned)
c) assigned_to
d) assigned_to for the status_code = 'A' if the date_assigned is > recdt


Assume for a moment that the set described above is called "data", then we want:

select vidno,
       substr( max( case when status_code = 'T' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) old_prev_reviewer,
       max(date_assigned) max_date_assigned,
       substr( max( case when date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) last_assigned,
       substr( max( case when status_code = 'A' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) curr_reviewer,
       to_date(substr( max( case when status_code = 'A' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || to_char(recdt,'yyyymmddhh24miss') end), 15 
),'yyyymmddhh24miss') curr_reviewer_recdt
  from assign
 where vidno in (select vidno from data)
 group by vidno;



(probably, I cannot really test anything with this "specification". If we call that assign_data, then what we want is:

select data.media,data.vidno,data.stage,data.status,data.recdt,data.created_date,
       assign_data.old_prev_reviewer, assign_data.max_date_assigned,  assign_data.last_assigned,
       case when assign_data.curr_reviewer_recdt >= data.recdt then assign_data.Curr_reviewer end 
Curr_reviewer
  from data, assign_data
 where data.vidno = assign_data.vidno(+)
 order by data.recdt, data.vidno


that gives the result set that is meaningfully named "E" above. If we have E, then

<code>
SEL
4 stars query   March 4, 2009 - 9am Central time zone
Reviewer: A reader 
Tom:

I forgot to describe what i am trying to get that helps you understand the query

MY main query (the one with rank ()) is based on the "QSTAGES" tables. THat finds each video that 
has a last stage of 0 approved. works fine.

Then i want to find the current assigned reviewer and previous assigned reviewer for that video.
The data is in "ASSIGN" table. This is where things slow down.

To determine that I check the highest assignment date "DATE_ASSIGNED" and compare it to the stage 
date "RECDT" i get from the main query because the assignment could have taken place BEFORE the new 
stage was added. This means there is NO current reviewer. If the assignment date was less than 
stage date then I pick the current reviewer and then the previous one (if exists) based on the 
logic i showed you.

There are also other filters I use in the view to freeze the list of records displayed if the first 
RECDT of the record was within 150 days of the last one. Also there is a filter on number of rows 
that can be displayed.


5 stars query   March 4, 2009 - 1pm Central time zone
Reviewer: A reader 
Tom:

yes, seq_no is primary key.
I should replace DENSE_RANK with ROW_NUMBER() but since seq_no is unique it will be the same but i 
will replace it anyhow.

I will test this out. But still do not you think an AFTER INSERT trigger on the "assign" table that 
would simply update two columns in the MASTER VIDEO table (prev_reviewer, cur_reviewer) would be 
easier.

My idea is that every time they make an assignment I update the VIDEO.PREV_REVIEWER with data in 
VIDEO.CURR_REVIEWER and then VIDEO.CURR_REVIEWER with the new person assigned. 

Then another AI trigger on QSTAGES would do the same after a new stage is addded (reset the current 
reviewer).

That would make it much easier for querying. Sometimes storing derived data makes some sense would 
it here.

thanks for your help


Followup   March 4, 2009 - 2pm Central time zone:

... But still do not you think an AFTER INSERT trigger on the
....

NO, I already said "no"
http://www.oracle.com/technology/oramag/oracle/08-sep/o58asktom.html



I have no problem with 'derived' data in general. Triggers = big trouble.

If you want to store the 'derived' data - do so by revoking INSERT on the tables from everyone, create a transactional API (stored procedure) to interact with these objects (not a TABLE api, a transctional interface, a series of stored procedures that do the transaction processing in it's entirety) and making it part of that.
5 stars query   March 4, 2009 - 2pm Central time zone
Reviewer: A reader 
Tom:

I read your article. it seems you had very bad experiences with triggers that got you to this 
point. 

So you are suggesting to populate the derived data fields in the client code that does the INSERT 
into assign table (instead of using AI oracle trigger). correct?

Another small thing i was thinking of. That main query where i scan the whole QSTAGES table and 
check any video that has last stage of "0" approved. I can also instead have create a FLAG in the 
master VIDEO table that I can set to imply that this video is to show up in the result set. When a 
new stage is added i reset this flag.

Do you like this approach. I guess the downpart if you keep using flags instead of queries the 
database will be have lots of FLAGS. but the advantage is you will get better performance and there 
would be no need to write complex SQL that a few people can write.


Followup   March 4, 2009 - 4pm Central time zone:

.. it seems you had very bad experiences with triggers that
got you to this point. ..

had? That would be past tense, this is an ongoing situation.

I did not say CLIENT code, I said "transactional api - the ONLY way to modify the data is via a transactional api that knows what it is doing, is well documented, easy to understand, straight-forward, and forgoes all MAGIC (triggers being of the black magic variety).


... but the advantage is you
will get better performance and there would be no need to write complex SQL
that a few people can write.
....

hah, and you want to write triggers? If you cannot master sql, you shouldn't be touching triggers at all.


I'm not a fan of your "flag" concept, no. I cannot really comment on your design, since - well - frankly - I don't understand or have your entire schema with me. You talk of a master video table - and I have no idea what you mean by that.


beware of maintaining this derived data, there are race conditions and multi-user situations you just haven't thought of - it is tricky stuff (and triggers would only make it harder to do correctly)
5 stars query   March 4, 2009 - 6pm Central time zone
Reviewer: A reader 
Tom:

I almost got this working, but i was not sure about why you used "RECDT" below.
this is not in the assign table. can be it a typo.

When i join "DATA" with "ASSIGN_DATA" it still can;t see RECDT because it is part of the join.

Also, I was using DATE_ASSIGNED but would not be faster to use the primary key "assign_no"
to find the max instead of to_char (dates) and then substring and checking id date_assigned is not 
null.



select vidno,
       substr( max( case when status_code = 'T' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) old_prev_reviewer,
       max(date_assigned) max_date_assigned,
       substr( max( case when date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) last_assigned,
       substr( max( case when status_code = 'A' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || assigned_to end), 15 ) curr_reviewer,
       to_date(substr( max( case when status_code = 'A' and date_assigned is not null then 
to_char(date_assigned,'yyyymmddhh24miss') || to_char(recdt,'yyyymmddhh24miss') end), 15 
),'yyyymmddhh24miss') curr_reviewer_recdt
  from assign
 where vidno in (select vidno from data)
 group by vidno;


Followup   March 4, 2009 - 8pm Central time zone:

... I almost got this working, but i was not sure about why you used "RECDT" below.
this is not in the assign table. can be it a typo.
...

well, you know what, I had NO CLUE, NO IDEA what was in or not in the assign table. You never *shared it* with me - did you.


when you figure out the approach I was taking, you'll know exactly what to put in there. And if you cannot, please do not use this query.
5 stars query   March 4, 2009 - 10pm Central time zone
Reviewer: A reader 
sure i did, i put it in the first comment above. I did not want to copy it again.
you are telling me you figured all of thsi out without seeing the table too.

create table assign (
 assign_no   number(10), --PK
 vidno        number(10),
 media       varchar2(3),
 assigned_to   varchar2(10),
 date_assigned  date,
 status_code   varchar2(1),
 created_Date  date)


Anyway, I understood your approach and followed it and you are truly amazing.

The old query was timing at 1:30 (one min 30 seconds).

With your way it ran in 4 seconds - with exactly same result

you are simply the best and the only mentor i ever had.

thanks a million

i guess no more scalar subqueries or triggers- use SETs and joins like you had it

the new thing to me was how you did the SUBSTR/MAX on the dates and assigned userid for the assign 
table. I never saw that in any book or column. just wondering if using ASSIGN_NO would be more 
efficient. I think i would need an index on (VIDNO, DATE_ASSIGNED).


Followup   March 5, 2009 - 1pm Central time zone:

I look at one thing, you cannot expect me to page up and down and try to reassemble everything. I look at the current review, and that is about it.



scalar subqueries are good for certain things. If you are calling plsql from sql, ALWAYS use a scalar subquery.

Eg: do not:

select f(x) 
  from t
 where g(y) = 42;



do:

select (select f(x) from dual) 
  from t
 where (select g(y) from dual) = 42;


search for scalar subquery caching on this site to read why.



another case is for pagination queries where by we could remove a lot of work form the inner query - and just do a quick scalar subquery for the resulting (few) rows.

for example, say you have a small table "owners" (few rows, or easily retrieved in sorted order via an index) and you have a big table of "owner objects"

In my example - big_table_owners is my "owners" (copy of all_users)
big_table is my "owner objects" (hundreds of thousands of rows or more, copy all_objects over and over)

You want to report on owners and the number of tables they have, even if it is ZERO (outer join). You are paging or doing a top-n - sorting by username in "owners"

You could code:


select *
  from (
select a.owner, count(b.owner)
  from big_table.big_table_owners a left join big_table.big_table b
    on (a.owner = b.owner and b.object_type = 'TABLE' )
 group by a.owner
 order by a.owner
       )
 where rownum <= 2
/

Or you could recognize that this is the same as:

select a.*,
       (select count(*)
          from big_table.big_table b
         where b.owner = a.owner and b.object_type = 'TABLE' ) cnt
  from (
select a.owner
  from big_table.big_table_owners a
 order by a.owner
       ) a
 where rownum <= 2
/


the inner query goes very fast, gets a small number of rows and then does (an index range scan, you'll have indexed owner and object_type obviously) to get the count




... I never saw that in any book or column. ...


There are thousands of techniques, if you know sql, if you think in sets, they just happen.

Look in Effective Oracle by Design, it has been written :) Chapter on Effective SQL - I demonstrate the exact 'technique' there.

3 stars Rownum vs where 1=2   March 6, 2009 - 2am Central time zone
Reviewer: DK from India
Tom,

We have a process where we are referring a table T and pulling the records.

Due to some reasons we want the table not to fecth the records from this table,while it does some 
other processings.

There are 2 options.

1.Use Select * from T where rownum <1
2.Use Select * from T where 1=2.

Which one of the 2 options will give better performance and why?

Thanks,
Dheeraj


Followup   March 6, 2009 - 10am Central time zone:

I prefer 1=2, but they should do the same amount of work (stage is copy of all objects)

ops$tkyte%ORA11GR1> select * from stage where rownum < 1;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3766266605

------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |   158 |     3  (34)| 00:00:
|*  1 |  COUNT STOPKEY     |       |       |       |            |
|   2 |   TABLE ACCESS FULL| STAGE | 68392 |    10M|     3  (34)| 00:00:
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<1)

Note
-----
   - dynamic sampling used for this statement


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
       1123  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

ops$tkyte%ORA11GR1> select * from stage where 1=2;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 1343009456

------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |   158 |     0   (0)|
|*  1 |  FILTER            |       |       |       |            |
|   2 |   TABLE ACCESS FULL| STAGE | 68392 |    10M|   284   (1)| 00:00:
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(NULL IS NOT NULL)

Note
-----
   - dynamic sampling used for this statement


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
       1123  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed




I like 1=2 because of the filter it puts there, it is so obvious "this will not happen" since "null is not null" is "not true"
5 stars query   March 7, 2009 - 10am Central time zone
Reviewer: A reader 
<<I'm not a fan of your "flag" concept, no. I cannot really comment on your design, since - well - 
frankly - I don't understand or have your entire schema with me. You talk of a master video table - 
and I have no idea what you mean by that. >>

Basically, you have a master video table. When a video arrives in, it is tested and the test result 
is stored in VIDEO_TEST. A record is also entered into QSTAGES table as stage "0", approved.

Now, it is ready for a human to review it.
I have a screen that requires to list every video that is ready for that review (any vide with last 
record is stage 0 ,approved) That is the SQL query i listed - which is basically sorting/ranking 
the records in QSTAGES  table and then checking if the last record is stage 0 and approved. When 
the reviewer enters a new stage number the video dissapears from screen.

My idea is that you can also have an ASSIGN_READY_FLAG in VIDEO table so that everytime a test is 
done you check that flag. Then the SQL query would not scan/sort the BIG QSTAGES table  but rather 
the VIDEO table which has one record per video. Then every time a reviewer enters a new stage 
(other than 0) i will reset that flag procedurally or via a trigger.

Would this make sense or you would still go with SQL? How do you usually decide on whether to 
create a FLAG (Y/N) versus writing somehow complex SQL to derive the result. I know you always like 
to write SQL. One disadvantage also is that ad-hoc users can only write very simple SQL so they 
can't get the derived result (unless i write a view for them). But then they come and start 
criticizing the db design as being very complex to deal with. How do you deal with those situations 
and explain to non-technical users that the design is correct and that is how they can get the 
result.

VIDEO
-------
vidno   NUMBER(10) (PK)
other_columns_details (70 columns)
assign_ready_flag??

VIDEO_TEST
------------
vidno
test_date
test_no
other_test_details

QSTAGES
-----------
seq_no number(10),
vidno  number(10) ,
media varchar2(3),
stage number(1),
recdt date,
compdt date,
status varchar2(1),
created_Date   date 


4 stars Cost Comparison   March 9, 2009 - 10am Central time zone
Reviewer: DK from India
Tom,

You said that you prefer "where 1=2" over "rownum <1"

1.When I see the plan generated by you 

Plan hash value: 1343009456(where 1=2)
The cost of Full table scan is 248
Plan hash value: 3766266605(where rownum <10
The cost of Full table scan is 0

2. I would expect the Cost of where 1=2 as 0 and Cost of where rownum < 1 to some value.

Please suggest.

Thanks,
Dheeraj




Followup   March 9, 2009 - 1pm Central time zone:

do you see the amount of work each did. forget the cost, you cannot compare the cost of two different queries really, cost is just a number.

they both did 0 IO's.

so, which one is more expensive? They did the *same exact thing*.

I like "1=2", it is "very obvious" what you are trying to do.
4 stars Pivot Query : Avoiding TABLE FULL ACCESS   March 10, 2009 - 3am Central time zone
Reviewer: Surajit chakraborty from India
how can we avoid TABLE FULL ACCESS in case of a pivot query.


Followup   March 11, 2009 - 2am Central time zone:

do something that uses an index?


no example
no context
no nothing


probably, your full scan is not only warranted - but the best possible plan for you, but, you fail to give any example


drop "in case of a pivot query" from your question, there is nothing special about a pivot query that would cause a full scan - your query is causing the full scan (and that might not be BAD at all, it might be the BEST way to process your query)
3 stars Avoiding TABLE FULL ACCESS in case of a pivot query   March 12, 2009 - 2am Central time zone
Reviewer: Surajit Chakraborty from India
Hi,

1) The following is the pivot query :

            select
                t3.ppv_policy_master_id policy_no,
                max(decode(t3.ppv_policy_param_id,
                (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='agency-code'),
                nvl(t3.ppv_policy_param_value,null),null)) agent_code_1,
                max(decode(t3.ppv_policy_param_id,
                (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='instalment-premium'),
                to_number(nvl(t3.ppv_policy_param_value,0)),0)) ins_prem
            from iims_uwr.t_policy_parameter_values t3 
            group by t3.ppv_policy_master_id 
            having
            max(decode(t3.ppv_policy_param_id,
            (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='dev-officer'),
            nvl(t3.ppv_policy_param_value,null),null))= '0006993' and
            max(decode(t3.ppv_policy_param_id,
            (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='date-of-commencement'),
            nvl(t3.ppv_policy_param_value,null),null))  between '20090304' and '20090304' and
            max(decode(t3.ppv_policy_param_id,
            (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='plan'),
            to_number(nvl(t3.ppv_policy_param_value,0)),0))= 14 and
            -- eliminate policies without agency code
            max(decode(t3.ppv_policy_param_id,
            (select t2.pp_policy_param_id from iims_config.t_policy_parameters t2 where 
t2.pp_parameter_name='agency-code'),
            nvl(t3.ppv_policy_param_value,null),null)) is not null;
/      

2) The following is the table structure :

-- Create table
create table T_POLICY_PARAMETER_VALUES
(
  PPV_POLICY_PARAM_ID    NUMBER(4) not null,
  PPV_POLICY_MASTER_ID   NUMBER(38) not null,
  PPV_POLICY_PARAM_VALUE VARCHAR2(100)
)
tablespace TS_IIMS_UWR_DAT_01
  pctfree 20
  initrans 1
  maxtrans 255
  storage
  (
    initial 12544K
    minextents 1
    maxextents unlimited
  );
-- Create/Recreate primary, unique and foreign key constraints 
alter table T_POLICY_PARAMETER_VALUES
  add constraint POLVALUES_PK primary key (PPV_POLICY_PARAM_ID,PPV_POLICY_MASTER_ID)
  using index 
  tablespace TS_IIMS_UWR_DAT_01
  pctfree 20
  initrans 2
  maxtrans 255
  storage
  (
    initial 12544K
    minextents 1
    maxextents unlimited
  );
alter table T_POLICY_PARAMETER_VALUES
  add constraint PPV_FK1 foreign key (PPV_POLICY_MASTER_ID)
  references T_POLICY_MASTER (POL_POLICY_MASTER_ID);
alter table T_POLICY_PARAMETER_VALUES
  add constraint PPV_FK2 foreign key (PPV_POLICY_PARAM_ID)
  references IIMS_CONFIG.T_POLICY_PARAMETERS (PP_POLICY_PARAM_ID);
-- Grant/Revoke object privileges 
grant select, insert, update, delete, references, alter, index on T_POLICY_PARAMETER_VALUES to 
PUBLIC;


3) The following is the plan :

Execution Plan
----------------------------------------------------------

--------------------------------------------------------------------------------
------

| Id  | Operation           | Name                      | Rows  | Bytes | Cost (
%CPU)|

--------------------------------------------------------------------------------
------

|   0 | SELECT STATEMENT    |                           |     1 |    19 |  1084
 (19)|

|   1 |  TABLE ACCESS FULL  | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

|   2 |  TABLE ACCESS FULL  | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

|   3 |  FILTER             |                           |       |       |
     |

|   4 |   SORT GROUP BY     |                           |     1 |    19 |  1084
 (19)|

|   5 |    TABLE ACCESS FULL| T_POLICY_PARAMETER_VALUES |  1039K|    18M|   922
  (5)|

|   6 |   TABLE ACCESS FULL | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

|   7 |   TABLE ACCESS FULL | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

|   8 |   TABLE ACCESS FULL | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

|   9 |   TABLE ACCESS FULL | T_POLICY_PARAMETERS       |     1 |    19 |    15
  (0)|

--------------------------------------------------------------------------------
------


Note
-----
   - 'PLAN_TABLE' is old version


Statistics
----------------------------------------------------------
          5  recursive calls
          3  db block gets
       4162  consistent gets
        583  physical reads
          0  redo size
        551  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
          1  rows processed



          I did even create an index on the column: PPV_POLICY_PARAM_VALUE of the table : table 
T_POLICY_PARAMETER_VALUES , but still couldnot avoid TABLE FULL ACCESS.

Thanks
Surajit


Followup   March 12, 2009 - 12pm Central time zone:

            from iims_uwr.t_policy_parameter_values t3 
            group by t3.ppv_policy_master_id 
            having


tell me how you would see that avoiding a full scan - given you want EVERY row from it? Forget the pivot, how would that avoid a full scan?


...I did even create an index on the column: PPV_POLICY_PARAM_VALUE of
the table : table T_POLICY_PARAMETER_VALUES ....

so, you do not use that column in a where clause anywhere? so what about the index?

5 stars pivot query   April 27, 2009 - 9am Central time zone
Reviewer: ravi from INDIA
i have data in table like this
id    parent_id
1       2
2       3
3       4
4       5
5       6
6       7

i need a query out put as below.
in where condition if i pass id=6
i have to get parent_id column as
7
6
5
4
3
2

please help me


Followup   April 27, 2009 - 2pm Central time zone:

no create table
no inserts

no sql from me.


But - before you just do that (add create and inserts) be sure to explain your output in a manner that someone other than you can actually determine what is happening.


If you pass in id=6, I can see you getting one row - parent_id=7

The rest of the output, no clue as to where/how/why it is there.
4 stars not sure but may be..   April 28, 2009 - 3am Central time zone
Reviewer: Hashmi 
If you want the parent_id corresponding to id then the query is as simple as

select parent_id from <yourtable> where id=5;

But most likely this is not your requirement.As per the output provided,may be this you want:

select distinct b.parent_id from(select parent_id,id from tbl
where parent_id<=(select max(parent_id)from tbl where id=5)
)b
order by b.parent_id desc
/


Followup   April 28, 2009 - 10am Central time zone:

it is best to not guess, you and I would just be wasting our time.

Unless and until there is a specification and a test case to work with, the person asking the question hasn't done their due diligence. 
3 stars pivot using clob getting ora-932   June 18, 2009 - 6pm Central time zone
Reviewer: Matt 
Hi Tom

I have data in a clob that I want to show after pivoting

Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options

create table interaction
(id number);
 
create table message
(id number,
 interaction_id number,
 is_request number);
 
create table data
(message_id number,
 data clob);
 
insert into interaction values (1);
insert into interaction values (2);
insert into message values (1,1,1);
insert into message values (2,1,0);
insert into message values (3,2,1);
insert into message values (4,2,0);
insert into data values (1,'this is request 1');
insert into data values (2,'this is reply 1');
insert into data values (3,'this is request 2');
insert into data values (2,'this is reply 2');


select i.id,
         max(decode(m.is_request,1,d.data,null)) request_data,
         max(decode(m.is_request,0,d.data,null)) reply_data
from interaction i, message m, data d
where m.interaction_id = i.id and d.message_id = m.id
group by i.id;


         max(decode(m.is_request,0,d.data,null)) reply_data
             *
ERROR at line 3:
ORA-00932: inconsistent datatypes: expected - got CLOB


Should be:

ID       request_data         reply_data
----------------------------------------
1       this is request 1     this is reply 1
2       this is request 2     this is reply 2


Thanks in advance

Followup   June 19, 2009 - 3pm Central time zone:

you cannot do that to a clob, it will not work with aggregates, not possible.

you can use dbms_lob.substr to get the first 4000 bytes if you want, if that suffices - otherwise you will not be pivoting this data.
5 stars Partial conditional Pivot   June 19, 2009 - 3am Central time zone
Reviewer: Sanjay Jha from Atlanta
I have a requirement like this:
Table has each row having 3 separate boolean type columns which need to be picked up while extraction and broken into separate rows, only if it is 'Y'. If all three are 'N', then one record needs to be generated. Any thoughts? I came out with this:
select distinct id_sess,featr_type_code from
(select id_sess,(case when featr_type.rowno=1 then DECODE(C_PORTOVERAGE_DISCOUNTABLE,'Y',3,0)
      when featr_type.rowno=2 then DECODE(C_MINIMUM_DISCOUNTABLE,'Y',2,0) 
      when featr_type.rowno=3 then DECODE(C_NOSHOW_DISCOUNTABLE,'Y',1,0) end) featr_type_code
from temp,(
        select rownum rowno
        from dual
        connect by level <= 3
  ) featr_type)


Followup   June 19, 2009 - 3pm Central time zone:

no create table
no inserts
no lookie



and this doesn't sound like a pivot. more like an un-pivot (turn columns into rows)


sounds like:

with data
as
(select level l from dual where level <= 3)
select *
from t, data
where (t.bool1 = 'N' and t.bool2 = 'N' and t.bool3 = 'N' and data.l = 1)
  OR (t.bool1 <> 'N' or t.bool2 <> 'N' or t.bool3 <> 'N')
/


to get 1 or 3 rows from each row
5 stars   July 8, 2009 - 5pm Central time zone
Reviewer: Reader 
create table tst
(subj varchar2(100),student varchar2(100), grade number);
 
insert into tst values('MATH', 'A',1);
insert into tst values('MATH', 'B',2);
insert into tst values('MATH', 'C',3);
insert into tst values('MATH', 'D',4);
insert into tst values('ENG', 'A',1); 
insert into tst values('ENG', 'B',2); 
insert into tst values('ENG', 'C',3); 
insert into tst values('ENG', 'D',4);

commit;

select * from tst

SUBJ  STU      GRADE
----- --- ----------
MATH  A            1
MATH  B            2
MATH  C            3
MATH  D            4
ENG   A            1
ENG   B            2
ENG   C            3
ENG   D            4

select subj,
       max(decode(student,'A',grade,null)) as student_a,
       max(decode(student,'B',grade,null)) as student_b,
       max(decode(student,'C',grade,null)) as student_c,
       max(decode(student,'D',grade,null)) as student_d   
from tst 
group by subj

I used the max-decode to get the the results as

SUBJ   STUDENT_A  STUDENT_B  STUDENT_C  STUDENT_D
----- ---------- ---------- ---------- ----------
MATH           1          2          3          4
ENG            1          2          3          4

I am trying to understand the logic used in the pivot. 
Question 1)Please tell why we need max in to get this
Question 2)If there are 1000's of students in the table, I have to use 1000's of max-decode. Is 
there anyway to generalize this? 


Followup   July 8, 2009 - 7pm Central time zone:

1) you could use min, you could use sum. you could use any aggregate that makes sense.

look at what you have without the group by/aggregate:

ops$tkyte%ORA10GR2> l
  1  select subj,
  2         (decode(student,'A',grade,null)) as student_a,
  3         (decode(student,'B',grade,null)) as student_b,
  4         (decode(student,'C',grade,null)) as student_c,
  5         (decode(student,'D',grade,null)) as student_d
  6* from tst
ops$tkyte%ORA10GR2> /

SUBJ  STUDENT_A  STUDENT_B  STUDENT_C  STUDENT_D
---- ---------- ---------- ---------- ----------
MATH          1
MATH                     2
MATH                                3
MATH                                           4
ENG           1
ENG                      2
ENG                                 3
ENG                                            4

8 rows selected.


since student is unique inside of a given subject, you can see what looks like a sparse matrix, we want to squish out the "null" cells. For student_a in subj math - only ONE row will have a value, same with student_b and so on.

So, if you group by subj - you get one row per subject and if you select the max(decode()), you get that single value for student a, b, c, d.



2) ... Is there anyway to generalize this? ...

no, a sql statement has a know number of columns - known at PARSE TIME. 
5 stars   July 9, 2009 - 12pm Central time zone
Reviewer: Reader 
Tom,
Are you mentioning that in Oracle SQL query, there will be a maximum number of columns and after 
that we cannot have any more columns?


Followup   July 14, 2009 - 3pm Central time zone:

there is a very very very reasonable limit of 1000 columns in a table or view.


a query however, can have many more than that


But that is not even close to what I said (I said nothing regarding any limit on the number of columns)

what I said was "AT PARSE TIME, SQL demands to know how many columns you are interested in, it is not possible to run a query that returns N columns an then to run the same query and get N+M columns later (where M is any integer other than zero)
5 stars Great job Tom   October 8, 2009 - 7am Central time zone
Reviewer: AC from Albany,USA
Hi Tom,
I have a typical requirement.
My table looks like
FBILL_NO              PCODE    AMT
------------------------- ---------- ----------
09-10/00000042            46      67
09-10/00000042            17    6715
09-10/00000043            46      39
09-10/00000043            38  15742.77
09-10/00000043            17    3896

Now the rquirement is to show the data as
FBILL_NO                  46        17        38
------------------------- ---------- ---------- ----------
09-10/00000042            67    6715      0
09-10/00000043            39    3896  15742.77

Here the number of records in PCODE column per FBILL_NO is dynamic.
Can you please help.
Regards,AC





Write a Review
 


All information and materials provided here are provided "as-is"; Oracle disclaims all express and implied warranties, including, the implied warranties of merchantability or fitness for a particular use. Oracle shall not be liable for any damages, including, direct, indirect, incidental, special or consequential damages for loss of profits, revenue, data or data use, incurred by you or any third party in connection with the use of this information or these materials.

About Oracle | Legal Notices and Terms of Use | Privacy Statement