Skip to Main Content

Breadcrumb

We're taking a break this week as Connor and Chris will both be at CloudWorld, the premier Oracle conference of 2024. If you're in Vegas, please come say Hi or pop into our sessions

Question and Answer

Tom Kyte

Thanks for the question, potential.

Asked: December 19, 2007 - 1:36 pm UTC

Last updated: December 21, 2007 - 2:55 pm UTC

Version: 9.2.0

Viewed 1000+ times

You Asked

Tom,

I have the following two tables:

table product (it grows when there is a new product being defined)
product_type description
p1 d1
p2 d2
... ...
p100 d100

table p_pt
person product_type
johndoe p1
johndoe p1
johndoe p1
johndoe p2
johndoe p3
janedoe p1
janedoe p2
janedoe p2
janedoe p3
janedoe p3
...

I need to run a report which outputs in the following format:
person d1 d2 d3
johndoe 3 1 1
janedoe 1 2 2

if table p_pt has one more row:
janedoe p4
then the report output will be:
person d1 d2 d3 d4
johndoe 3 1 1 0
janedoe 1 2 2 1

It is the aggregation-then-pivot, the difficult part is the determination of the dynamic headings of those columns, i.e., d1, d2, d3, d4. I don't want show all the possible d1 to d100 when they are sparsely populated. Please give me some suggestion how I can achieve the above.

Thanks,

and Tom said...

You would have to

a) run a query to figure out what columns you wanted
b) use the output of a) to dynamically construct the query you desire.


A sql statement must

a) have a fixed number of columns
b) with static column names

at parse (prepare) time. Given your requirement, you would have to dynamically generate such a query.

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:124812348063#41097616566309

Rating

  (4 ratings)

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

Comments

SQL solution

Michel Cadot, December 20, 2007 - 4:22 am UTC


Follow two solutions in SQL depending if you know the maximum number of product types or not.
SQL> select * from p_pt order by 1, 2;

PERSON     PRODUCT_TY
---------- ----------
janedoe    p1
janedoe    p2
janedoe    p2
janedoe    p3
janedoe    p3
johndoe    p1
johndoe    p1
johndoe    p1
johndoe    p2
johndoe    p3

SQL> -- Maximum number of columns known, say 5
SQL> set head off
SQL> set feed off
SQL> col nop noprint
SQL> col person format a10
SQL> col val format a3
SQL> with 
  2    product_types as (
  3      select distinct 
  4             product_type,
  5             dense_rank () over (order by product_type) rk
  6      from p_pt
  7    )
  8  select 1 nop, 'person' person, 
  9         lpad(max(decode(rk,1,product_type)),3) val,
 10         lpad(max(decode(rk,2,product_type)),3) val,
 11         lpad(max(decode(rk,3,product_type)),3) val,
 12         lpad(max(decode(rk,4,product_type)),3) val,
 13         lpad(max(decode(rk,5,product_type)),3) val
 14  from product_types
 15  union all
 16  select 2 nop, person, 
 17         to_char(sum(decode(a.product_type||'/1',b.product_type||'/'||b.rk,1)),'99') val,
 18         to_char(sum(decode(a.product_type||'/2',b.product_type||'/'||b.rk,1)),'99') val,
 19         to_char(sum(decode(a.product_type||'/3',b.product_type||'/'||b.rk,1)),'99') val,
 20         to_char(sum(decode(a.product_type||'/4',b.product_type||'/'||b.rk,1)),'99') val,
 21         to_char(sum(decode(a.product_type||'/5',b.product_type||'/'||b.rk,1)),'99') val
 22  from p_pt a, product_types b
 23  group by person
 24  order by nop, person
 25  /

person      p1  p2  p3
janedoe      1   2   2
johndoe      3   1   1

SQL> insert into p_pt values ('janedoe', 'p4');
SQL> with 
  2    product_types as (
  3      select distinct 
  4             product_type,
  5             dense_rank () over (order by product_type) rk
  6      from p_pt
  7    )
  8  select 1 nop, 'person' person, 
  9         lpad(max(decode(rk,1,product_type)),3) val,
 10         lpad(max(decode(rk,2,product_type)),3) val,
 11         lpad(max(decode(rk,3,product_type)),3) val,
 12         lpad(max(decode(rk,4,product_type)),3) val,
 13         lpad(max(decode(rk,5,product_type)),3) val
 14  from product_types
 15  union all
 16  select 2 nop, person, 
 17         to_char(sum(decode(a.product_type||'/1',b.product_type||'/'||b.rk,1)),'99') val,
 18         to_char(sum(decode(a.product_type||'/2',b.product_type||'/'||b.rk,1)),'99') val,
 19         to_char(sum(decode(a.product_type||'/3',b.product_type||'/'||b.rk,1)),'99') val,
 20         to_char(sum(decode(a.product_type||'/4',b.product_type||'/'||b.rk,1)),'99') val,
 21         to_char(sum(decode(a.product_type||'/5',b.product_type||'/'||b.rk,1)),'99') val
 22  from p_pt a, product_types b
 23  group by person
 24  order by nop, person
 25  /

person      p1  p2  p3  p4
janedoe      1   2   2   1
johndoe      3   1   1

SQL> rollback;
SQL> -- Maximum number of columns unknown
SQL> col val format a80
SQL> with 
  2    product_types as (
  3      select distinct 
  4             product_type,
  5             dense_rank () over (order by product_type) rk,
  6             count(distinct product_type) over () cnt
  7      from p_pt
  8    ),
  9    persons as ( select distinct person from p_pt )
 10  select 1 nop, 'person' person,
 11         translate(substr(sys_connect_by_path(lpad(product_type,3),'/'),2),'/',' ') val
 12  from product_types
 13  where rk = cnt
 14  connect by prior rk = rk - 1
 15  start with rk = 1
 16  union all
 17  select 2 nop, person,
 18         translate(substr(sys_connect_by_path(to_char(val,'99'),'/'),2),'/',' ') val
 19  from ( select a.person, b.rk, b.cnt,
 20                nvl((select sum(1) from p_pt c 
 21                     where c.person = a.person and c.product_type = b.product_type)
 22                    ,0) val
 23         from persons a, product_types b
 24         group by a.person, b.product_type, b.rk, b.cnt
 25       )
 26  where rk = cnt
 27  connect by prior rk = rk - 1 and prior person = person
 28  start with rk = 1
 29  order by nop, person
 30  /

person      p1  p2  p3
janedoe      1   2   2
johndoe      3   1   1

SQL> insert into p_pt values ('janedoe', 'p4');
SQL> with 
  2    product_types as (
  3      select distinct 
  4             product_type,
  5             dense_rank () over (order by product_type) rk,
  6             count(distinct product_type) over () cnt
  7      from p_pt
  8    ),
  9    persons as ( select distinct person from p_pt )
 10  select 1 nop, 'person' person,
 11         translate(substr(sys_connect_by_path(lpad(product_type,3),'/'),2),'/',' ') val
 12  from product_types
 13  where rk = cnt
 14  connect by prior rk = rk - 1
 15  start with rk = 1
 16  union all
 17  select 2 nop, person,
 18         translate(substr(sys_connect_by_path(to_char(val,'99'),'/'),2),'/',' ') val
 19  from ( select a.person, b.rk, b.cnt,
 20                nvl((select sum(1) from p_pt c 
 21                     where c.person = a.person and c.product_type = b.product_type)
 22                    ,0) val
 23         from persons a, product_types b
 24         group by a.person, b.product_type, b.rk, b.cnt
 25       )
 26  where rk = cnt
 27  connect by prior rk = rk - 1 and prior person = person
 28  start with rk = 1
 29  order by nop, person
 30  /

person      p1  p2  p3  p4
janedoe      1   2   2   1
johndoe      3   1   1   0

In this second query, I gave another way to get the right row using count (rk=cnt) instead of max.

Regards
Michel

Tom Kyte
December 20, 2007 - 10:08 am UTC

but you have not made the column names person, p1, p2, p3, p4

you have made a row of data be that (and therefore coerce all of the values in the result set to be strings).

potential, December 20, 2007 - 9:28 am UTC

Great solutions. Many thanks to Tom and Michel.

SQL solution

Michel Cadot, December 20, 2007 - 11:27 am UTC


You're right Tom but "potential" talked about report.
Are not reports just strings?

Im' a little bit insincere on this one. :)

Regards
Michel
Tom Kyte
December 20, 2007 - 2:34 pm UTC

:)

I think most reporting tools would dump the column headings as part of their processing - they wouldn't "know" to look at the first row.

11g pivot clause?

Kim Berg Hansen, December 21, 2007 - 3:59 am UTC

I know the original question was for version 9.
But I'm curious about the pivot clause in 11g.

As far as I can tell the pivot clause requires that you list the columns and names for the columns in the pivot_for_clause.
So even with 11g, the solution to this question would still require two steps - first find the possible values/columnnames, then build a dynamic sql statement using that list in the pivot_for_clause?
Or is it possible to do it differently in 11g?

A theoretical sub-question: Do you think it technologically possible, that in a future release, the pivot clause would permit a subquery as pivot_for_clause, thus making this possible in one step?

Thanks always for sharing your genius :-)

Regards

Kim Berg Hansen

Tom Kyte
December 21, 2007 - 2:55 pm UTC

SQL needs to know

a) the number of columns being selected
b) their names
c) their types

it cannot vary from execution to execution

I doubt the SQL people (that make the standard) will change those facts...