Skip to Main Content
  • Questions
  • Yup, ANOTHER Rows to Columns (or vice-versa) question. Sorry.

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question.

Asked: June 04, 2008 - 1:14 pm UTC

Last updated: April 30, 2009 - 10:25 am UTC

Version: 9.2.0

Viewed 1000+ times

You Asked

First things first - I've done the search part! Some very usefull stuff indeed to pick up along the way, but somehow couldn't find the cure for my very simple problem, so (the code is SQL Server, but should be done in O-10g):

Create table Test (TestID int, Name varchar(50), Phone varchar(20))

Insert into Test values(1, 'John', '1234567')
Insert into Test values(2, 'Peter', '9876543')

So, I have this:

TestID Name Phone
------ ------- ----------
1 John 1234567
2 Peter 9876543

Now, what I need is a query (to become a view or stored procedure, eventually) that gives an output like this:

Row1 Row2
---------- ----------
1 2
John Peter
1234567 9876543

I can't believe how simple it looks, yet I am stuck with it for two days now (yeah, I know, the word is - embarrassing).

Nevertheless, what I have managed to acomplish so far (by using a new temp table and a cursor) looks like this:

Row1 Row2
---------- ----------
1 NULL
John NULL
1234567 NULL
NULL 2
NULL Peter
NULL 9876543

Question 1: How to query this "result table" to show it in a needed form?
Question 2: What is a best way to do the whole thing from scratch?
Question 3: Is this possible to achieve at run time, without knowing the number of rows and columns (i.e. by passing variables in a procedure call - dynamically)?

If you could shed some light on this for me, do it, please. Thank you. Cheers.

and Tom said...

well, this is quite hard actually - and requires at least TWO steps

step 1: figure out how many columns there will be. That is, run a count(*) against the table.

step 2: dynamically construct a query that'll select out that many columns


because every query must MUST know the number of columns it has at parse time. It is required.


So, let us presume we know "2" is the number of rows. then:

ops$tkyte%ORA9IR2> select *
  2    from (select testid, name, phone, row_number() over (order by testid) rn
  3            from test),
  4         (select level RW from dual connect by level <= 3)
  5  /

    TESTID Tablespace Name     PHONE                        RN         RW
---------- ------------------- -------------------- ---------- ----------
         1 John                1234567                       1          1
         2 Peter               9876543                       2          1
         1 John                1234567                       1          2
         2 Peter               9876543                       2          2
         1 John                1234567                       1          3
         2 Peter               9876543                       2          3

6 rows selected.


we need to output each row in the source set as many times as we have columns - that is what the cartesian join to "(select level RW from dual connect by level <= 3)" does. We also need to assign a row number to each row in the source data set - that is what row_number() does.

Next:
ops$tkyte%ORA9IR2> select rw,
  2         decode( rn, 1, decode( rw, 1, to_char(testid), 2, name, 3, phone) ) r1,
  3         decode( rn, 2, decode( rw, 1, to_char(testid), 2, name, 3, phone) ) r2
  4    from (select testid, name, phone, row_number() over (order by testid) rn
  5            from test),
  6         (select level RW from dual connect by level <= 3)
  7  /

        RW R1         R2
---------- ---------- ----------
         1 1
         1            2
         2 John
         2            Peter
         3 1234567
         3            9876543

6 rows selected.


we get something like where you are now - but we have the RW column - to group by....

and then:


ops$tkyte%ORA9IR2> select rw,
  2         max(decode( rn, 1, decode( rw, 1, to_char(testid), 2, name, 3, phone) )) r1,
  3         max(decode( rn, 2, decode( rw, 1, to_char(testid), 2, name, 3, phone) )) r2
  4    from (select testid, name, phone, row_number() over (order by testid) rn
  5            from test),
  6         (select level RW from dual connect by level <= 3)
  7   group by rw
  8  /

        RW R1         R2
---------- ---------- ----------
         1 1          2
         2 John       Peter
         3 1234567    9876543





In 11gR1, this will be simplified to:

ops$tkyte%ORA11GR1> with data
  2  as
  3  (
  4  select rn, data, thing
  5    from (select to_char(testid) testid, name, phone, to_char(rownum) rn from test )
  6     unpivot ( data for thing in ( testid , name, phone) )
  7  )
  8  select thing, row1, row2
  9    from data
 10  pivot( max(data) for rn in ( '1' as row1, '2' as row2 ))
 11  /

THING  ROW1       ROW2
------ ---------- ----------
TESTID 1          2
PHONE  1234567    9876543
NAME   John       Peter



and line 10 is the part you would have to dynamically construct....

Rating

  (6 ratings)

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

Comments

Too good

illiyaz Mohammad, June 04, 2008 - 2:36 pm UTC

Too good Tom....Keep up the excellent work....

No further questions (YOU rest MY case :D)

A reader, June 04, 2008 - 3:02 pm UTC

Great answer! And fast one, too.

Being an Oracle newbie (3-4 years of MSSQLS experience, though), now I am about to dive deep into the documentation to learn what most of those commands actually do (decode, pivot, unpivot, to name a few).

Thank you, very much and take care.

when number of rows is dynamic

Shanthi, April 30, 2009 - 6:50 am UTC

How do we proceed when we are have n no of rows. If there dynamic way to add that ?

select rw,
max(decode( rn, 1, decode( rw, 1, to_char(C1), 2, C2, 3, C3) )) r1,
max(decode( rn, 2, decode( rw, 1, to_char(C1), 2, C2, 3, C3) )) r2,
max(decode( rn, 3, decode( rw, 1, to_char(C1), 2, C2, 3, C3) )) r3
from (select C1, C2, C3, row_number() over (order by C1) rn
from TEMPCR),
(select level RW from dual connect by level <=( SELECT COUNT(1)+1 FROM TEMPCR))
group by rw

where tempcr is my table Count(1)+1 gives me the rows count+1 for column name
Tom Kyte
April 30, 2009 - 10:25 am UTC

the number of columns a sql statement returns must be known at PARSE (compile time).


how to proceed? re-read the original answer, it tells you how to do this.



well, this is quite hard actually - and requires at least TWO steps

step 1: figure out how many columns there will be. That is, run a count(*) against the table.

step 2: dynamically construct a query that'll select out that many columns

yet another way. But not for n rows

Shanthi, April 30, 2009 - 6:57 am UTC

SELECT COLUMN_NAME,
MAX(DECODE(ROW_ID,1,DECODE(COLUMN_ID,1,C1,2,C2,3,C3)) )R1,
MAX(DECODE(ROW_ID,2,DECODE(COLUMN_ID,1,C1,2,C2,3,C3)) )R2
FROM (SELECT M.*,ROWNUM ROW_ID FROM TEMPCR M) TEMPR,USER_TAB_COLUMNS U
WHERE TABLE_NAME = 'TEMPCR'
GROUP BY column_name ORDER BY COLUMN_NAME

PROCEDURE TO CONVERT ANY TABLE COLUMNS TO ROWS

Shanthi, April 30, 2009 - 7:39 am UTC

CREATE OR REPLACE PROCEDURE Col_to_Rows (P_TNAME VARCHAR2,CUR_OUTPUT OUT SYS_REFCURSOR)
AS
--create or replace procedure Col_to_Rows(TNAME IN VARCHAR2(30),cur_output OUT SYS_REFCURSOR) AS
SQLSTMT VARCHAR2(3000);
SQLSTMT_ROWS VARCHAR2(3000);
SQLSTMT_COLS VARCHAR2(3000);
RCOUNT NUMBER;
RIND NUMBER;
CURSOR CUR_COL_LIST(P_TNAME VARCHAR2) IS SELECT ROWNUM || ',' || COLUMN_NAME COLUMN_ITEM FROM USER_TAB_COLUMNS WHERE TABLE_NAME= P_TNAME;

REC_COL_LIST CUR_COL_LIST%ROWTYPE;

BEGIN
SQLSTMT := 'SELECT COUNT(1) FROM ' || P_TNAME ;
EXECUTE IMMEDIATE SQLSTMT INTO RCOUNT;

SQLSTMT_COLS :=NULL;

OPEN CUR_COL_LIST(P_TNAME) ;
LOOP
FETCH CUR_COL_LIST INTO REC_COL_LIST;
EXIT WHEN CUR_COL_LIST%NOTFOUND;
IF SQLSTMT_COLS IS NULL THEN
SQLSTMT_COLS :=REC_COL_LIST.COLUMN_ITEM ;
ELSE
SQLSTMT_COLS := SQLSTMT_COLS || ',' || REC_COL_LIST.COLUMN_ITEM;
END IF;
END LOOP;


SQLSTMT_ROWS :=NULL;
RIND :=1;
WHILE RIND <= RCOUNT
LOOP
IF SQLSTMT_ROWS IS NULL THEN
SQLSTMT_ROWS := 'MAX(DECODE(ROW_ID,' || RIND || ',DECODE(COLUMN_ID,'|| SQLSTMT_COLS || '))) R' || RIND;
ELSE
SQLSTMT_ROWS := SQLSTMT_ROWS || ',MAX(DECODE(ROW_ID,' || RIND || ',DECODE(COLUMN_ID,'|| SQLSTMT_COLS || '))) R' || RIND;
END IF;
RIND := RIND+1;

SQLSTMT := 'SELECT COLUMN_NAME,' || SQLSTMT_ROWS || ' FROM (SELECT M.*,ROWNUM ROW_ID FROM '|| P_TNAME || ' M) TEMPTBL,USER_TAB_COLUMNS GTBL
WHERE GTBL.TABLE_NAME = '''||P_TNAME || ''' GROUP BY COLUMN_NAME ORDER BY COLUMN_NAME';
DBMS_OUTPUT.PUT_LINE( SQLSTMT );
OPEN cur_output FOR SQLSTMT ;
END LOOP;
end ;

Ravi, May 01, 2009 - 11:24 am UTC

Shanthi, it's nice .... your query gives the column names as well. Just a modified version of your query to suit the above table.

select column_name, max(decode(row_id,1,decode(column_id,1,to_char(testid),2,name,3,phone))) r1, max(decode(row_id,2,decode(column_id,1,to_char(testid),2,name,3,phone))) r2 from (select m.*,rownum row_id from test m) tempr,user_tab_columns u
where table_name = 'TEST'
group by column_name
order by column_name

Tom,
Here are the number of rows are limited... can we write a generic query instead of a procedure ? Say the number of rows in the table keep changing ...

Thanks,
Ravi

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.