
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.
Examples
For our three examples we are going to use the following query as a base:
1 | SQL> SELECT o.order_mode |
3 | 3 , SUM (o.order_total) order_total |
5 | 5 GROUP BY o.order_mode, o.order_status |
6 | 6 ORDER BY o.order_mode |
10 | ORDER_MODE ORDER_STATUS ORDER_TOTAL |
What we want to do is to present the data like this:
1 | ORDER_MODE
STAT_0 STAT_1 STAT_2 STAT_3 STAT_4 STAT_5
STAT_6 STAT_7 STAT_8 STAT_9 STAT_10 |
3 | DIRECT
163131.3 227569.5 166169.5 206659.4 56352.5 172586.2
115968 33617.1 545300.5 205674.2 10601 |
4 | 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.
1 | SQL> SELECT UPPER (o.order_mode) order_mode |
2 | 2 , SUM (DECODE(o.order_status, 0, o.order_total)) stat_0 |
3 | 3 , SUM (DECODE(o.order_status, 1, o.order_total)) stat_1 |
4 | 4 , SUM (DECODE(o.order_status, 2, o.order_total)) stat_2 |
5 | 5 , SUM (DECODE(o.order_status, 3, o.order_total)) stat_3 |
6 | 6 , SUM (DECODE(o.order_status, 4, o.order_total)) stat_4 |
7 | 7 , SUM (DECODE(o.order_status, 5, o.order_total)) stat_5 |
8 | 8 , SUM (DECODE(o.order_status, 6, o.order_total)) stat_6 |
9 | 9 , SUM (DECODE(o.order_status, 7, o.order_total)) stat_7 |
10 | 10 , SUM (DECODE(o.order_status, 8, o.order_total)) stat_8 |
11 | 11 , SUM (DECODE(o.order_status, 9, o.order_total)) stat_9 |
12 | 12 , SUM (DECODE(o.order_status, 10, o.order_total)) stat_10 |
14 | 14 GROUP BY o.order_mode |
18 | ORDER_MODE
STAT_0 STAT_1 STAT_2 STAT_3 STAT_4 STAT_5
STAT_6 STAT_7 STAT_8 STAT_9 STAT_10 |
20 | DIRECT
163131.3 227569.5 166169.5 206659.4 56352.5 172586.2
115968 33617.1 545300.5 205674.2 10601 |
21 | 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:
1 | SQL> col scenario format a10 |
To show a nice crosstab version of our data, the query below would do the trick:
2 | 2 , MAX (DECODE(x.currency, 'CAD' , x.rate)) CAD_rate |
3 | 3 , MAX (DECODE(x.currency, 'MYR' , x.rate)) MYR_rate |
4 | 4 , MAX (DECODE(x.currency, 'NOK' , x.rate)) NOK_rate |
5 | 5 , MAX (DECODE(x.currency, 'USD' , x.rate)) USD_rate |
12 | 12 GROUP BY x.scenario |
15 | SCENARIO CAD_RATE MYR_RATE NOK_RATE USD_RATE |
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.
1 | SQL> SELECT UPPER (order_mode) |
14 | 14 SELECT o.order_mode |
16 | 16 , SUM (o.order_total) order_total |
18 | 18 GROUP BY o.order_mode, o.order_status |
21 | 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) |
25 | 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 |
27 | DIRECT
163131.3 227569.5 166169.5 206659.4 56352.5 172586.2
115968 33617.1 545300.5 205674.2 10601 |
28 | 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.
1 | SQL> WITH main_query AS ( |
4 | 4 , SUM (o.order_total) order_total |
6 | 6 GROUP BY o.order_mode, o.order_status |
8 | 8 SELECT UPPER (x.order_mode) order_mode |
9 | 9 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 0 ) stat_0 |
10 | 10 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 1 ) stat_1 |
11 | 11 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 2 ) stat_2 |
12 | 12 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 3 ) stat_3 |
13 | 13 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 4 ) stat_4 |
14 | 14 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 5 ) stat_5 |
15 | 15 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 6 ) stat_6 |
16 | 16 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 7 ) stat_7 |
17 | 17 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 8 ) stat_8 |
18 | 18 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 9 ) stat_9 |
19 | 19 ,( SELECT y.order_total FROM main_query y WHERE y.order_mode = x.order_mode AND y.order_status = 10 ) stat_10 |
21 | 21 SELECT DISTINCT m.order_mode |
26 | ORDER_MODE
STAT_0 STAT_1 STAT_2 STAT_3 STAT_4 STAT_5
STAT_6 STAT_7 STAT_8 STAT_9 STAT_10 |
28 | DIRECT
163131.3 227569.5 166169.5 206659.4 56352.5 172586.2
115968 33617.1 545300.5 205674.2 10601 |
29 | 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.
I feel Informatica is the best and most useful aspect through which complex IT problems get solved very easily.
ReplyDeleteInformatica Read Soap Api