Get the latest!
You are here: Home / Tutorials / Three Ways To Transpose Rows Into Columns in Oracle SQL
Three Ways To Transpose Rows Into Columns in Oracle SQL

Three Ways To Transpose Rows Into Columns in Oracle SQL

Have you ever been in a situation where you as a developer knows that your data is stored in your table rows, but your customer don’t care…wanting to present the data as columns?

In this tutorial I will discuss three different variants on how to transpose table rows into columns. By using SUM + DECODE, the new Oracle 11g PIVOT operator, and by using WITH + sub SELECTs.

Introduction

According to Wikipedia®, one definition of the word “transpose”  is:

A matrix obtained from a given matrix by interchanging each row and the corresponding column.

There are many technical variants on what transposing really is, but in our case it is simply to present row values as columns. This is very helpful if you i.e. want to present data in a crosstab, or matrix. An example can i.e. be that you have your order table where each row has an order date, and you want to present the sales per month (extracted from the order date) in columns. A very common situation in Data Warehouse reporting.

This functionality has existed a long time in Excel (transpose when you use “Paste Special..“), reporting tools like Web Intelligence (Business Objects), etc.

In Oracle it has been possible to mimic this functionality for a long time…but until version 11g, it had not been a thought through functionality.

We are going to look at three of the most common ways to do row to column transposing.

UPDATE 3/12/2013: If you are looking for the opposite, transpose columns to rows, I have added a new tutorial about this. Please take a look here: How To Transpose Columns To Rows In Oracle

Examples

For our three examples we are going to use the following query as a base:

SQL> SELECT	o.order_mode
  2          ,o.order_status
  3          ,SUM(o.order_total) order_total
  4  FROM	orders o
  5  GROUP BY o.order_mode, o.order_status
  6  ORDER BY o.order_mode
  7          ,o.order_status
  8  /

ORDER_MODE ORDER_STATUS ORDER_TOTAL
---------- ------------ -----------
direct                0    163131.3
direct                1    227569.5
direct                2    166169.5
direct                3    206659.4
direct                4     56352.5
direct                5    172586.2
direct                6      115968
direct                7     33617.1
direct                8    545300.5
direct                9    205674.2
direct               10       10601
online                0     25976.7
online                2    103834.4
online                3     56381.7
online                4    700068.1
online                5    183261.2
online                6     90411.8
online                8    322192.5
online                9     57062.4
online               10    225236.7

20 rows selected

What we want to do is to present the data like this:

ORDER_MODE     STAT_0     STAT_1     STAT_2     STAT_3     STAT_4     STAT_5     STAT_6     STAT_7     STAT_8     STAT_9    STAT_10
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
DIRECT       163131.3   227569.5   166169.5   206659.4    56352.5   172586.2     115968    33617.1   545300.5   205674.2      10601
ONLINE        25976.7              103834.4    56381.7   700068.1   183261.2    90411.8              322192.5    57062.4   225236.7

Ok, let’s get down to some business…

 

Example # 1a: Transpose rows into columns using SUM and DECODE to summarize data

Before version 11g of Oracle this method of transposing data was probably the most proficient. It is actually the exact query that generated the data result shown above.

SQL> SELECT	UPPER(o.order_mode) order_mode
  2  		,SUM(DECODE(o.order_status, 0, o.order_total)) stat_0
  3  		,SUM(DECODE(o.order_status, 1, o.order_total)) stat_1
  4  		,SUM(DECODE(o.order_status, 2, o.order_total)) stat_2
  5  		,SUM(DECODE(o.order_status, 3, o.order_total)) stat_3
  6  		,SUM(DECODE(o.order_status, 4, o.order_total)) stat_4
  7  		,SUM(DECODE(o.order_status, 5, o.order_total)) stat_5
  8  		,SUM(DECODE(o.order_status, 6, o.order_total)) stat_6
  9  		,SUM(DECODE(o.order_status, 7, o.order_total)) stat_7
 10  		,SUM(DECODE(o.order_status, 8, o.order_total)) stat_8
 11  		,SUM(DECODE(o.order_status, 9, o.order_total)) stat_9
 12  		,SUM(DECODE(o.order_status, 10, o.order_total)) stat_10
 13  FROM	orders o
 14  GROUP BY o.order_mode
 15  ORDER BY 1
 16  /

ORDER_MODE     STAT_0     STAT_1     STAT_2     STAT_3     STAT_4     STAT_5     STAT_6     STAT_7     STAT_8     STAT_9    STAT_10
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
DIRECT       163131.3   227569.5   166169.5   206659.4    56352.5   172586.2     115968    33617.1   545300.5   205674.2      10601
ONLINE        25976.7              103834.4    56381.7   700068.1   183261.2    90411.8              322192.5    57062.4   225236.7

As you probably see, for each of the eleven possible order_status values, there is a “stat_x” line. If we had twelve values…well…then it would have been twelve “stat_x” fields. This is actually one of the situations where I find it easier to use a DECODE function, rather than CASE. If you want to know about the difference between CASE, and DECODE – please take a look at the post named “Oracle DECODE and CASE: What is the difference“.

 

Example # 1b: Transpose rows into columns using MAX and DECODE – for non-summarized data

I want to show another variant of this (above) query as well.

Let us pretend we are going to help our MLM (Multi Level Marketing) company with calculating commission for our distributors, and we want to calculate everything using three different rate scenarios to be able to measure the commission impact against each other. And, we just want to show all the rates in a nice way, grouped by each scenario.

Ok, this is the data we start out with:

SQL> col scenario format a10
SQL> SELECT	r.scenario
  2          ,r.currency
  3          ,r.rate
  4  FROM	rates r
  5  /

  SCENARIO CURRENCY       RATE
---------- -------- ----------
         1 USD            1.00
         1 CAD            1.45
         1 MYR            3.34
         1 NOK            6.08
         2 USD            1.00
         2 CAD            1.47
         2 MYR            3.47
         2 NOK            6.12
         3 USD            1.00
         3 CAD            1.51
         3 MYR            2.98
         3 NOK            5.96

12 rows selected

To show a nice crosstab version of our data, the query below would do the trick:

SQL> SELECT	x.scenario
  2  		,MAX(DECODE(x.currency, 'CAD', x.rate)) CAD_rate
  3  		,MAX(DECODE(x.currency, 'MYR', x.rate)) MYR_rate
  4  		,MAX(DECODE(x.currency, 'NOK', x.rate)) NOK_rate
  5  		,MAX(DECODE(x.currency, 'USD', x.rate)) USD_rate
  6  FROM	(
  7              SELECT	r.scenario
  8                      ,r.currency
  9                      ,r.rate
 10              FROM	rates r
 11  		) x
 12  GROUP BY x.scenario
 13  /

  SCENARIO   CAD_RATE   MYR_RATE   NOK_RATE   USD_RATE
---------- ---------- ---------- ---------- ----------
         1       1.45       3.34       6.08          1
         2       1.47       3.47       6.12          1
         3       1.51       2.98       5.96          1

BTW – The rates shown are totally random…

 

Example # 2:  Transpose rows into columns using the Oracle PIVOT operator

The PIVOT and the UNPIVOT operators were introduced in Oracle version 11g. The following query will give the same result as the query above, just by using the PIVOT operator.

SQL> SELECT	UPPER(order_mode)
  2  		,stat_0
  3  		,stat_1
  4  		,stat_2
  5  		,stat_3
  6  		,stat_4
  7  		,stat_5
  8  		,stat_6
  9  		,stat_7
 10  		,stat_8
 11  		,stat_9
 12  		,stat_10
 13  FROM	(
 14              SELECT	o.order_mode
 15                      ,o.order_status
 16                      ,SUM(o.order_total) order_total
 17              FROM	orders o
 18              GROUP BY o.order_mode, o.order_status
 19  		)
 20  PIVOT	(
 21  			SUM(order_total) FOR order_status IN (0 stat_0, 1 stat_1, 2 stat_2, 3 stat_3, 4 stat_4, 5 stat_5, 6 stat_6, 7 stat_7, 8 stat_8, 9 stat_9, 10 stat_10)
 22  )
 23  /

UPPER(ORDER_MODE)     STAT_0     STAT_1     STAT_2     STAT_3     STAT_4     STAT_5     STAT_6     STAT_7     STAT_8     STAT_9    STAT_10
----------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
DIRECT              163131.3   227569.5   166169.5   206659.4    56352.5   172586.2     115968    33617.1   545300.5   205674.2      10601
ONLINE               25976.7              103834.4    56381.7   700068.1   183261.2    90411.8              322192.5    57062.4   225236.7

 

Example # 3: Transpose rows into columns using WITH and sub SELECTS

Ok, in this example we are going to put our main query inside a WITH statement, and then query the WITH materialized resultset multiple times. Using WITH, the main resultset wil not be recomputed multiple times.

SQL> WITH main_query AS (
  2                          SELECT	o.order_mode
  3                                  ,o.order_status
  4                                  ,SUM(o.order_total) order_total
  5                          FROM	orders o
  6                          GROUP BY o.order_mode, o.order_status
  7  )
  8  SELECT	UPPER(x.order_mode) order_mode
  9  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 0 ) stat_0
 10  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 1 ) stat_1
 11  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 2 ) stat_2
 12  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 3 ) stat_3
 13  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 4 ) stat_4
 14  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 5 ) stat_5
 15  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 6 ) stat_6
 16  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 7 ) stat_7
 17  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 8 ) stat_8
 18  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 9 ) stat_9
 19  		,(SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 10 ) stat_10
 20  FROM	(
 21              SELECT	DISTINCT m.order_mode
 22              FROM	main_query m
 23  		) x
 24  /

ORDER_MODE     STAT_0     STAT_1     STAT_2     STAT_3     STAT_4     STAT_5     STAT_6     STAT_7     STAT_8     STAT_9    STAT_10
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
DIRECT       163131.3   227569.5   166169.5   206659.4    56352.5   172586.2     115968    33617.1   545300.5   205674.2      10601
ONLINE        25976.7              103834.4    56381.7   700068.1   183261.2    90411.8              322192.5    57062.4   225236.7

 

Well, there you have it. Three different ways to get the same data.

 

Other ways to solve the problem

Although only three different methods are presented here, there are actually other ways to transpose the data from rows to columns. One of them is to use a WITH statement, and then multiple UNIONs with a SUM of all columns at the end. I will let it be up to you to try this one out. If you really would like to know how to do this, leave a comment below, and I will give you a sample. It is a more cumbersome (reason for not initially showing a sample) way to do the transposing this way.

Yet another possibility, after my understanding, is to use XML in your query. I will give a sample on our PL/SQL site for this one.

 

Conclusion

As  you can see, there are a few ways to to transpose rows into columns in Oracle. If you have a different..or even better way to do this…we will all love to hear about it. The comment field is made just for you. ;-)

BTW – If you wonder about the data used in the examples, they are from the “OE” sample schema that comes with Oracle. It is normally locked and expired by default.

 

Hope this help you out in your work,

TJ

About TJ Abrahamsen

TJ Abrahamsen is the founder and author of OracleTuts. He has worked with Oracle since 1997. His expertise is with Oracle SQL and PL/SQL. He loves teaching others, and like to share his thoughts, ideas, and experience on his blog.
  • Fahmi

    Hi TJ,
    Thanks for the fantastic tutorial. The DECODE version really helped with my work a lot. But I was wondering, is there any way to transpose data where the number of stats are unknown? Say for example the number of ORDER_STATUS changes over time, is there any way to rig the query so that it does not have to be rewritten for the increase in STAT_X?

    • http://oracletuts.net TJ Abrahamsen

      Hello there Fahmi. Thank you for your feedback. I have gotten a similar question via my contact form earlier.

      As far as I know, the only way you can do this is to use Dynamic SQL in PL/SQL – where you build your query as a string based on the status codes.

      But, depending on exactly what you need to display, there might be some other options that I have not tried out – for this purpose.

      1. Google LISTAGG and PIVOT.

      2. It is possible to create a function that i.e. returns a comma separated list of weekly orders for a customer, as one column

      3. I saw on the Internet that someone mentioned that you could create a view / materialized view.

      But, again…it all depends on your exact need.

      Hope this helps,
      TJ

  • rashed

    Hi Abraham,
    your article is great. I learned a lot from it.
    but, what if i don’t know how many rows or what are the rows to transpose? For example, the data changed daily.

    thanks very much,
    Rashed

    • http://oracletuts.net TJ Abrahamsen

      Hey Rashed. :-) I have gotten this question several times, and every time I seem to answer that as far as I know, the only solution would be to use PL/SQL and dynamic SQL.

      But, I will see if I can come up with something else.

      ~ TJ

  • Bari

    Good work,
    Thanks for explaining transpose query,have been searching for this one for a while.

  • Anand

    TJ. this is amazing thanks. I do have one more question. Do you know of an easy way where i can do the following with a WITH command?
    JAN FEB MAR APR MAY
    ONLINE ESTIMATED
    TOTAL
    DIRECT ESTIMATED
    TOTAL

    • http://oracletuts.net TJ Abrahamsen

      Hey there Anand -

      I am not totally sure what you mean…your formatting might not show correctly…

      But, if you have all data available in your WITH statement, you can i.e. use UNION ALL to select parts from your WITH statement…

      ~ TJ

  • Marty Williams

    This tutorial was great, and very well written. Now is there a way to have total columns on the right and at the bottom, so, for example, we have a total for all Directs, and a total for all stat01 below the stat01 column?

    • http://oracletuts.net TJ Abrahamsen

      Hi Marty – I am sorry that I have not gotten back to you sooner. Have several deadlines these days. I am not totally certain I understand what you mean. Would you be able to paste in a sample of what the data should look like?

      ~ TJ

  • PM

    Hey TJ, REALLY appreciated your precious time and effort to write so clear and so many great ideas.
    Decode was not recognized by the sql server bu WITH as main query did.
    Thanks a million
    PM

    • http://oracletuts.net TJ Abrahamsen

      Hi PM -

      Thank you for your feedback. I am glad it was helpful for you.

      ~ TJ

  • shobhit

    Hi TJ,
    These are great explanations.
    can you also provide some feedback about the performance comparisons among these three ways and also the union option. i mean which one is better and and takes lesser time.

    • http://oracletuts.net TJ Abrahamsen

      Hello shobhit -
      Thank you for your feedback. Remember do do a Facebook “like” :-) .
      I have not done any specific performance comparison on these, but I can see what I come up with.
      We use the Oracle PIVOT variant (example # 3) at my work all the time, with lots of data, and it seems to work great. I will see if I can do some explain plans on the different ones.

      ~ TJ

      • Zubair Ali

        Hi TJ Abrahamsen,

        its really good article with data example and queries, really nice.

        You can use XML PIVOT to generate N columns without specifying the constant no of columns. but little tricky.

        Thanks

        • http://oracletuts.net TJ Abrahamsen

          Hello Zubair -

          Thank you for your comment, and your tip!

          ~ TJ

  • Pingback: How To Transpose Columns To Rows In Oracle - OracleTuts

Scroll To Top