Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Chris.

Asked: May 01, 2007 - 11:03 am UTC

Last updated: October 28, 2017 - 2:40 am UTC

Version: 10g

Viewed 1000+ times

You Asked

Tom,

I see lots of references to turning rows into columns, but how about turning columns into rows? Obviously I can select x union select y union select z, but that is only realistic for a small number of columns. What would you suggest for a large number of columns?

What I'm trying to accomplish is to pass information to a web service via a ref cursor which will give them a list of column names and their values for a single row in a table. For example, if I want to have them display all of the elements from the dba_scheduler_job vertically, how could I accomplish that?

I have written the following simple function which works, but I'm wondering if there is a more technically elegant and potentially less I/O intensive way? I thought maybe something to do with analytics, but I couldn't see an obvious approach there either.



function get_details(P_JOB_NAME in varchar2) return details_tab_t pipelined is

v_details_rec details_t;

begin
-- get the job info from the database
-- find the column names to be spooled out
for rec in (select column_name
from dba_tab_columns
where table_name = 'DBA_SCHEDULER_JOBS'
order by column_id)
loop
v_details_rec.item_name := rec.column_name;

execute immediate 'select ' || rec.column_name ||
' from dba_scheduler_jobs ' ||
' where job_name = ''' || P_JOB_NAME || ''''
into v_details_rec.item_value;

pipe row (v_details_rec);
end loop;

return;
end get_details;

and Tom said...

Now, in the following code, which I use in sqlplus infrequently - i said "didn't do cursor_sharing..."

You might want to or to add bind variable support in some other fashion!!!

ops$tkyte%ORA9IR2> create or replace type myScalarType as object
  2  ( rnum number, cname varchar2(30), val varchar2(4000) )
  3  /

Type created.

ops$tkyte%ORA9IR2> create or replace type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte%ORA9IR2>
ops$tkyte%ORA9IR2> create or replace
  2  function cols_as_rows( p_query in varchar2 ) return myTableType
  3  -- this function is designed to be installed ONCE per database, and
  4  -- it is nice to have ROLES active for the dynamic sql, hence the
  5  -- AUTHID CURRENT_USER
  6  authid current_user
  7  -- this function is a pipelined function -- meaning, it'll send
  8  -- rows back to the client before getting the last row itself
  9  -- in 8i, we cannot do this
 10  PIPELINED
 11  as
 12      l_theCursor     integer default dbms_sql.open_cursor;
 13      l_columnValue   varchar2(4000);
 14      l_status        integer;
 15      l_colCnt        number default 0;
 16      l_descTbl       dbms_sql.desc_tab;
 17      l_rnum          number := 1;
 18  begin
 19          -- parse, describe and define the query.  Note, unlike print_table
 20          -- i am not altering the session in this routine.  the
 21          -- caller would use TO_CHAR() on dates to format and if they
 22          -- want, they would set cursor_sharing.  This routine would
 23          -- be called rather infrequently, I did not see the need
 24          -- to set cursor sharing therefore.
 25      dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
 26      dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
 27      for i in 1 .. l_colCnt loop
 28          dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
 29      end loop;
 30
 31          -- Now, execute the query and fetch the rows.  Iterate over
 32          -- the columns and "pipe" each column out as a separate row
 33          -- in the loop.  increment the row counter after each
 34          -- dbms_sql row
 35      l_status := dbms_sql.execute(l_theCursor);
 36      while ( dbms_sql.fetch_rows(l_theCursor) > 0 )
 37      loop
 38          for i in 1 .. l_colCnt
 39          loop
 40              dbms_sql.column_value( l_theCursor, i, l_columnValue );
 41              pipe row
 42              (myScalarType( l_rnum, l_descTbl(i).col_name, l_columnValue ));
 43          end loop;
 44          l_rnum := l_rnum+1;
 45      end loop;
 46
 47          -- clean up and return...
 48      dbms_sql.close_cursor(l_theCursor);
 49      return;
 50  end cols_as_rows;
 51  /

Function created.

ops$tkyte%ORA9IR2>
ops$tkyte%ORA9IR2> select *
  2    from TABLE( cols_as_rows('select *
  3                                from emp
  4                               where rownum = 1') );

      RNUM CNAME                          VAL
---------- ------------------------------ --------------------
         1 EMPNO                          7369
         1 ENAME                          SMITH
         1 JOB                            CLERK
         1 MGR                            7902
         1 HIREDATE                       17-DEC-80
         1 SAL                            800
         1 COMM
         1 DEPTNO                         20

8 rows selected.

Rating

  (4 ratings)

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

Comments

A reader, May 02, 2007 - 4:56 pm UTC


Global search on tables

Tom Admirer, May 02, 2007 - 6:40 pm UTC

Tom,
The above solution was very good. A little variation on the theme of Print_table.

My question here might be little off the topic. But please excuse... I would like to search a table for a certain value. This search is not narrowed down to a particular column instead all columns in the table should be considered.

Typically a procedure/function could take parameters like table_name and search_string and return all the rows whenever any columns are matched with the search_string. Is there any efficient way to achieve this?

Any help/suggestions on this would be greatly appreciated.


Example :

SQL > exec search_table(emp,'7698')
     EMPNO ENAME      JOB              MGR HIREDATE           SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ----------- ---------- ---------- ----------
      7499 ALLEN      SALESMAN        7698 20-FEB-1981       1600        300         30
      7521 WARD       SALESMAN        7698 22-FEB-1981       1250        500         30
      7654 MARTIN     SALESMAN        7698 28-SEP-1981       1250       1400         30
      7698 BLAKE      MANAGER         7839 01-MAY-1981       2850                    30
      7844 TURNER     SALESMAN        7698 08-SEP-1981       1500          0         30
      7900 JAMES      CLERK           7698 03-DEC-1981        950                    30


Thanks in advance.


Tom Kyte
May 03, 2007 - 7:27 am UTC

... Is there any efficient way to achieve this? ...

not really.

this is the thing full scans are made of. You might be able to use a text index on all of the columns concatenated together - text would create an inverted list index (taking your columns and turning them into rows that point back to the single row).

Table search query

Tom Admirer, May 03, 2007 - 7:44 pm UTC

Tom,

Thanks for your valuable suggestion.
I made use of your function and came up with this query to achieve my purpose...it does serve my purpose. But I am not sure of its performance. Or should I care of its performance when a full table scan is almost inevitable?

Comments?


select * from (select row_number() over (order by NULL) rn,a.* from emp a) where rn in (select distinct rnum from table(cols_as_rows('select * from emp')) where to_char(val)='7698') order by rn

scott@MYDB>/

        RN      EMPNO ENAME      JOB              MGR HIREDATE           SAL    
---------- ---------- ---------- --------- ---------- ----------- ---------- 
         2       7499 ALLEN      SALESMAN        7698 20-FEB-1981       1600    
         3       7521 WARD       SALESMAN        7698 22-FEB-1981       1250    
         5       7654 MARTIN     SALESMAN        7698 28-SEP-1981       1250    
         6       7698 BLAKE      MANAGER         7839 01-MAY-1981       2850    
        10       7844 TURNER     SALESMAN        7698 08-SEP-1981       1500    
        12       7900 JAMES      CLERK           7698 03-DEC-1981        950    


Tom Kyte
May 04, 2007 - 12:50 pm UTC

one would prefer

select * from emp
where to_char(empno) = '.....'
or ename = '....'
or job = '....'


over a pivot and unpivot - you would hit that table multiple times.

Issues with returned data

Robert Chambers, October 27, 2017 - 3:43 pm UTC

I am using this on a view, which is a query on a DBLink into Postgres that has a long column containing data under 100 characters a timestamp.

It works great converting the LONG into varchar2.

But if my query returns 5000 records, every other set of 50 records returned have the first character missing.

I discovered that the DBLink may we the cause... If I exclude the LONG column from the query the exact same thing happens.

My view on the Postgres table includes all the column in order:
create or replace view v_pg_message_property as
select "property_key" property_key, "label" label, "value" value, "message_key" message_key
from "message_property"@pg_link_ems

The view defined the columns as
property_key number(28,6)
label varchar2(255)
value LONG
message_key number(28,6)

Any reason why the data would clip ?

where "label" = 'Resolved_Date';

Connor McDonald
October 28, 2017 - 2:40 am UTC

I'm not sure how this is in any way related to the original question

More to Explore

PL/SQL demos

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

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library