Dynamic SQL Pivoting – Stealing Anton’s Thunder

15

I have a colleague who has a stroke of genius. He can create things in SQL and PL/SQL that I find very hard to fathom, let alone could have come up with myself. His name is Anton and his recent focus has been Pivoting in SQL. The process of turning a Query Result upside down to present it differently. Turning the set of values in one of the columns into the column headings for the entire query.

Like taking the following query result:

and turning it into this structure:

Anton recently wrote a brief, somewhat cryptic article on Pivoting SQL Queries on this weblog; see:http://technology.amis.nl/blog/?p=1197 . In this article, Anton introduced an approach to pivoting that does not require us to hard-code all the values that we want to see appear in our column-headings (such as the values for JOB in the example above). However, the OBJECT TYPE that he uses in this article contains the hardcode query itself. It was already quite good, but he went where man did not go before and created an implementation that is completely dynamic. We simply pass in the query we want to have pivoted, and the function Anton created obliges.

In this article I will show a few examples of what we can do with this amazing gem as well as make the code itself available for download. However, and this is the last time I will blow his trumpet, it is not my code. I am good at writing long-winded articles, while Anton forges some source.

....
The first query result in the article was created using a pretty straightforward query:

select deptno
,      job
,      avg(sal) sal_avg
from   emp
group
by     deptno
,      job
/

The second, pivoted result was created using the same query, however this time passing it as a string to the pivot function I will introduce in more detail later on:

select *
from   table( pivot(' select deptno
                      ,      job
                      ,      avg(sal) sal_avg
                      from   emp
                      group
                      by     deptno
                      ,      job
                    ')
            )

We have seen the stunning result of this call already. To prove it is really working I can add two rows to our EMP table:

insert into emp
( empno, ename, job, deptno, sal)
values
( 1111, 'ANTON', 'WIZARD', 10, 1991)
/
insert into emp
( empno, ename, job, deptno, sal)
values
( 1112, 'TOBIAS', 'SALESMAN', 40, 1100)
/

Now if we rerun the last query, the result will be:

So we have picked up the new records and extracted the new JOB value (WIZARD) that was added as a new column in our query result. Note: sometimes new column values are not picked up so readily. In those cases, we have to cheat a little by enticing the CBO into re-evaluating our query (or so it seems, we have not quite figured it out what it is). If we add a space somewhere in the query-string or make another change, the new column values are picked up.

Generating a Distance Matrix

Your average roadmap will have a grid-like figure, a distance matrix, that lists cities and the distances between them. Using Anton’s Pivot function, it turns out to be pretty simple to create such a Distance Matrix. Let’s review an hypothetical example (the distances are by no means correct, but that’s not the point) for the main cities in The Netherlands:

The western part of The Netherlands is called De Randstad. This is where almost half the population lives and most economical activity is concentrated. The map for this piece (blue on the map because it is below sea level) of our country:

 

We now want to build ourselves a little distance matrix for the four main cities in De Randstad. We create a small database structure for the city-and-connection details:

To have easier access to our data, we create a utility view:

create or replace view city_connections
as
select from_city.name from_city
,      to_city.name   to_city
,      conn.distance  distance
from   cities from_city
,      connections conn
,      cities to_city
where  conn.from_id = from_city.id
and    conn.to_id = to_city.id
union
select from_city.name from_city
,      to_city.name   to_city
,      conn.distance  distance
from   cities from_city
,      connections conn
,      cities to_city
where  conn.to_id = from_city.id
and    conn.from_id = to_city.id
/

A simple query against this view renders these results:

FROM_CITY    TO_CITY   DISTANCE
------------ ------------------
AMSTERDAM    ROTTERDAM       22
AMSTERDAM    THE HAGUE       25
AMSTERDAM    UTRECHT         25
ROTTERDAM    AMSTERDAM       22
ROTTERDAM    THE HAGUE       35
ROTTERDAM    UTRECHT         15
THE HAGUE    AMSTERDAM       25
THE HAGUE    ROTTERDAM       35
THE HAGUE    UTRECHT         45
UTRECHT      AMSTERDAM       25
UTRECHT      ROTTERDAM       15
UTRECHT      THE HAGUE       45

The pivot-query we need is the following:

select * from table( pivot(  'select * from city_connections' ) )
/

and its results

Of course we feel the need to prove the pudding once again, by adding the fifth city in the country – Nieuwegein (well, it not really the fifth city but it is where we (AMIS) are located as a company:

<code>insert into cities ( id, name) values ( 5, 'NIEUWEGEIN') / insert into connections ( from_id , to_id , distance ) values ( 5, 2, 5) / </code>

Note only a single connection, between Utrecht and Nieuwegein. On the map, this looks like:

Now will our pivot-query smoothly extend to include Nieuwegein?

And it does! (of course I knew it would otherwise I would not have raise the subject).

Using that magical pivot function

You may have grasped how we make use of that pivot function. It is pretty simple really: we pass it a varchar2 that contains a query. The query needs to query at least three columns. More is allowed. The columns can have any data type – NUMBER, VARCHAR2, DATE, INTERVAL, TIMESTAMP etc. Not sure about BLOB though – probably not a good idea.

The last column in the query you feed the pivot function turns up in the cells of the matrix. The one but last column provides the values for the columns in the matrix. The other columns are (all) used as row-label.

So for one more example: we would like to see which roles have privileges on which tables. In our schema, we have the tables CAREER, CITIES, DEPT, EMP and POINTS. We have granted access privileges to various roles and we would like an overview. We know that such information is available from the Data Dictionary View USER_TAB_PRIVS. This view has columns GRANTEE and TABLE_NAME that are useful to us. We want Roles in the Rows of our matrix and Tables in the columns. So the query we offer up for pivoting should select three columns of which the role is the first one, the table_name the second and the third should be whatever we want to display in the cells:

select * from table( pivot( 'select grantee role
                              ,      table_name
                              ,      ''   *    ''
                              from   user_tab_privs
                             ')
                    )
 /

Note: in this case we simply mark the cell if the role has a (any) privilege on the table. We could refine this to show the entire CRUD palet (for CREATE, RETRIEVE, UPDATE and DELETE privileges).

How it works

I really couldn’t tell you. Well, I can tell you some of it. But I am also trying to get my head round it.

The function pivot is defined as follows:

create or replace
function pivot
( p_stmt in varchar2
, p_fmt in varchar2 := 'upper(@p@)'
, dummy in number := 0
) return anydataset pipelined using PivotImpl;
/

The essence of the pivoting is clearly not in the function, but in the PivotImpl Type it leverages. The function is defined as pipelined, spitting out records one by one instead of as a whole bunch, and returns an AnyDataSet. Because of that return type, we can regard this function as a Table Function that we can use with the TABLE operator in a FROM clause: select * from TABLE( TABLE_FUN()).

The Pipelined Table Function is implemented with the Interface Approach, requires the user to supply a type that implements a predefined Oracle interface consisting of start, fetch, and close operations. See for details: Chapter 13 – Pipelined Table Functions in the Oracle Data Cartridge Guide.

 

The interface approach requires the user to supply a type that implements a predefined Oracle interface consisting of start, fetch, and close operations. The type is associated with the table function when the table function is created. During query execution, the fetch method is invoked repeatedly to iteratively retrieve the results. With the interface approach, the methods of the implementation type associated with the table function can be implemented in any of the supported internal or external languages (including PL/SQL, C/C++, and Java).

Resources

Download the sources for this article: AntonsPivoting.zip. Connected as SCOTT, first run pivotFun.sql, then either pivot.sql or pivot_distances.sql.

Also check out the discussion on AskTom (September 2012) on this article: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:4843682300346852395#5394721000346803830.

Oracle® Data Cartridge Developer’s Guide 10g Release 1 (10.1) – Chapter 13 Using Pipelined and Parallel Table Functions

Oracle Data Cartridge Developer’s Guide – Chapter 17 Pipelined Table Functions: Interface Approach Example

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

15 Comments

  1. i hav a table of this format:

    Id                   Descrip.
    1                note1 note2 note3
    2                note1 note2 note3
    3                note1 note2 note3

    i want the output as :

    Id                   Descrip.

    1                    note1
    note2
    note3

    2                    note1
    note2
    note3

  2. Hi,

    Many thanks for this excellent code, when i am  using this code with storedproc following error is showing

    PL/SQL: ORA-22905: cannot access rows from a non-nested table item

    OPEN porec_rpt FOR
          SELECT *
            FROM TABLE
                    (pivot
                        (   ‘select * from document’
                        )
                    );

    could you please help

    Regards

  3. Hi,
    Many thanks for this fantastic piece of work
     I got the same ORA-29913: error in executing ODCITABLEDESCRIBE callout’ err. How may i resolve this.
    With best wishes
    Olu

  4. This is a brilliant piece conceptual explanation and code.  I had a similar  context about , delivering an output of  an arbitrary sql string , using parallel pipelined  functions which accepts a character string as input. I had a tough time to make it parallel as the plsql syntax more or less demanded an input ref cursor for the parallel operation to click , and I had to give it up.

  5. Great work guys! I used it and it helped a lot..
    I got the same ORA-29913: error in executing ODCITABLEDESCRIBE callout’ err,
    after digging in a bit I saw it is sensitive to reserved oracle words in the pivot column (the n-1 column). when I changed ‘user’ and ‘rowid’ values into ‘username’ and ‘row_id’ it worked fine.
    another thing regarding that n-1 pivot column – can’t extend beyond 30 chars or it will pop the “invalid … ” err.

    after fixing those two all I was left with was admiring your nice work.

  6. Hi,
    I tried implementing the code.Even after creating the type PIVOTIMPL,when I run the select statement,i get an error message that PIVOTIMPL is not declared.
    Any inputs what could be the reason?

  7. I get the error “PLS-00302 SETUROWID must be declared”

    when dbms_types.typecode_urowid then
    dbms_sql.column_value( self.cur, i, ur1 );
    outset.seturowid( ur1 );

  8. For all you people experiencing problems with the ODCITABLEDESCRIBE callout, you might want to check two things. The number columns are quite strict: a column for the vertical axis, a column for the horizontal axis, and a third one for the result values.
    Secondly, I haven’t found a way to parametize the query results, e.g. concatenating a parameter in the query. If you stick to that, it works like a charm!

  9. Hi, Is there a way of getting this to work on oracle 9i? I managed to make it compile somehow but I get the same ‘ORA-29913: error in executing ODCITABLEDESCRIBE callout’ error message when I try to run it.

  10. Anton, Lucas, did you guys ever tried this by building a dynamic array? Or does this have to many disadvanges?

  11. Hi,
    that’s really a kind o f magic.
    now i understand the implementation via interface.
    it’s an interface mechanism like in C#; as you must satisfy those oci prefixed interface and then internally in the sql-engine methods of the interfaces are called.
    a really interesting approach.
    Karl