How To Convert Rows To Columns and Back Again With SQL (Aka PIVOT and UNPIVOT) - Oracle All Things SQL Blog
How To Convert Rows To Columns and Back Again With SQL (Aka PIVOT and UNPIVOT) - Oracle All Things SQL Blog
ANALYTICAL SQL
September 8, 2016
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 1/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
The Olympics is over for another year. But there's still plenty of time for SQL-style
data wrangling of the results!
To do this, I've compiled a table of medal winners from Rio for each sport:
This is great when looking for a specific result. But what everyone really wants to
know how their country fared overall. To get this you need to convert the table
above to the final medal table:
This post will teach you. You'll also learn various row and column transformations
with SQL including:
If you want to play along you can access the scripts in LiveSQL. Or you can nab
the create table scripts at the bottom of this post.
Oracle Database 11g introduced the pivot operator. This makes switching rows to
columns easy. To use this you need three things:
. The column that has the values defining the new columns
. What these defining values are
. What to show in the new columns
The value in the new columns must be an aggregate. For example, count, sum,
min, etc. Place a pivot clause containing these items after the table name, like so:
So to create the final medal table from the raw data, you need to plug in:
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 3/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Hmmm, that's not right! You wanted the total medals for each country. This is
giving the results per athlete!
This is because Oracle adds an implicit group by for all the columns not in the
pivot clause. To avoid this, use an inline view that selects just the columns you
want in the results:
select * from (
select noc, medal from olympic_medal_winners
)
pivot (
count(*) for medal in ( 'Gold' gold, 'Silver' silver, 'Bronze' bronze )
)
order by 2 desc, 3 desc, 4 desc
fetch first 5 rows only;
And voila!
This is looking promising. But it's still not right. China didn't finish second. Team GB
did! And all countries have too many medals.
Doubles events.
In these cases multiple people win a medal. And each person has their own entry
in the winners table. So for sports like badminton, tennis and rowing there are
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 4/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
multiple rows for some events. But the medal table is counted per event, not per
person!
To overcome this, instead of counting all rows you need to get the number of
different events. Do this by changing the count(*) to a count (distinct). The distinct
contains the columns defining a particular event. In this case that's sport, gender
and event.
Include these in inline view and update the pivot clause, giving:
select * from (
select noc, medal, sport, event, gender
from olympic_medal_winners
)
pivot (
count(distinct sport ||'#'|| event ||'#'||gender )
for medal in ( 'Gold' gold, 'Silver' silver, 'Bronze' bronze )
)
order by 2 desc, 3 desc, 4 desc
fetch first 5 rows only;
That's better!
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 5/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Now you've got your medal table. But if you're a true data geek like me you'll want
to include other things in the total. For example:
How many different sports did each country win their medals in?
What were the names of the athletes winning each medal?
etc.
Including these in the pivot is easy. All you need to do is add more aggregate
functions to your SQL!
For the count of sports per medal, this is count(distinct sport). For the athlete
names you can use listagg().
select * from (
select noc, medal, sport, event, gender, athlete
from olympic_medal_winners
)
pivot (
count( distinct sport ||'#'|| event ||'#'|| gender ) medals,
count( distinct sport ) sports,
listagg( athlete, ',') within group (order by athlete) athletes
for medal in ( 'Gold' gold )
)
where gold_medals > 1
order by gold_medals, gold_sports, noc
fetch first 5 rows only;
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 6/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Note that for the athlete's name, you get an entry per medal won. So people like
Michael Phelps and Jason Kenny who won multiple medals will appear several
times!
To overcome this you can slap a regular expression around the gold_athletes
column. For example:
When pivoting multiple functions, be aware that Oracle prefixes each generated
column with the alias you provide in the "in" clause. Without the alias Oracle uses
the values from the source column. So with many aggregates the new columns will
all have the same name. This leads to nasty
select * from (
select noc, medal, sport, event, gender, athlete
from olympic_medal_winners
)
pivot (
count( distinct sport ||'#'|| event ||'#'|| gender ) medals,
count( distinct sport ) sports,
listagg( athlete, ',') within group (order by athlete) athletes
for medal in ( 'Gold' gold )
)
where noc like 'D%'
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 7/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
order by gold_medals;
min('X')
select * from (
select noc, sport
from olympic_medal_winners
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 8/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
)
pivot (min('X') for sport in (
'Archery' as arc, 'Athletics' as ath, 'Hockey' as hoc,
'Judo' as jud, 'Sailing' as sai, 'Wrestling' as wre
)
)
order by noc
fetch first 7 rows only;
Note you don't have to list out all the sports. Just those you want in your matrix.
Rows from other sports are excluded from the final table. This rule applies to all
forms of pivot. Only rows which match values in the in list appear in the results.
Common problems
So far so good. But there are a few issues you may hit when pivoting. The first is
that this feature is only available in Oracle Database 11g and above. So if you're on
earlier versions you need a different approach.
Manual Pivoting
If pivot isn't available in your database you'll have to do it the old-school way:
manually. For each column you want to create in the final results you need to:
. Check whether the current value of the pivoting column equals the value you
want
. If it does, return the value you want to aggregate. Otherwise null
. Apply you aggregation to the result of this
For example, the old fashioned way to define columns for the medals table is:
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 9/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
count ( case when medal = 'Bronze' then 1 end ) bronze_medals
To split the values out, just add a group by for the columns you want to count
each value by. The final SQL statement then looks like:
select noc,
count ( case when medal = 'Gold' then 1 end ) gold_medals,
count ( case when medal = 'Silver' then 1 end ) silver_medals,
count ( case when medal = 'Bronze' then 1 end ) bronze_medals
from olympic_medal_winners
group by noc
order by 2 desc, 3 desc, 4 desc
fetch first 5 rows only;
Convoluted, isn't it? And to fix the double counting athletes problem you need an
even more obscure:
count ( distinct case when medal = 'Gold' then sport ||'#'|| event ||'#'|| gender
end )
For the Olympics this happens at most every four years. So changing your SQL is
low effort.
But this could become a pain if the values often change. Fortunately there are
ways around this.
Dynamic SQL
If you know the columns will change regularly, it may be best to go with dynamic
SQL. With this you generate the pivot values on the fly. All you need is a SQL
query providing these in a comma-separated list.
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 10/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Listagg is perfect here. For example, to generate a list of all the contested sports,
use:
select listagg('''' || sport || ''' as ' || sport, ',') within group (order by
sport)
from (select distinct sport from olympic_medal_winners);
You can then pop this into a dynamic SQL statement, such as:
declare
sql_stmt clob;
pivot_clause clob;
begin
select listagg('''' || sport || ''' as "' || sport || '"', ',') within group
(order by sport)
into pivot_clause
from (select distinct sport from olympic_medal_winners);
Like pivot, listagg is only available in 11g and up. So if you're stuck in the dark ages
on an earlier version, check out Tom Kyte's stragg function.
This works. But it can be fiddly to write. Especially if you have a complex pivot. And
the number of columns can change on each execution. Which makes fetching the
results tricky.
On top of all this you've got an extra query to find the columns. So this could slow
you down.
Fortunately there's a method that's far easier to write, fetch the results from and is
a single query!
XML PIVOTing
To use this, just specify XML after the pivot keyword. Then place a subquery in the
values clause. And you're done!
This gives you the results as an XML document. Each column is in an item tag:
select * from (
select noc, sport
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 11/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
from olympic_medal_winners
)
pivot xml (count(*) medal_winners for sport in (
select sport
from olympic_medal_winners
where sport like 'A%')
)
where rownum = 1;
NOC SPORT_XML
ALG <?xml version="1.0"
encoding="UTF-8"?>
<PivotSet>
<item>
<column
name="SPORT">Archery</column>
<column
name="MEDAL_WINNERS">1</column>
</item>
<item>
<column
name="SPORT">Artistic Gymnastics</column>
<column
name="MEDAL_WINNERS">1</column>
</item>
<item>
<column
name="SPORT">Athletics</column>
<column
name="MEDAL_WINNERS">2</column>
</item>
</PivotSet>
At this point the Olympic geeks among you may be shouting "But Algeria didn't
win any medals in archery or gymnastics in 2016! Why is this giving them one in
each?"
Count(*) returns how many rows there are. There's one row per country. So this
will give every country at least one medal in each sport!
To avoid this you need to count a specific column. Crucially, this needs to be
empty if a country has no medals in a given event. This is because count(column)
only returns the number of non-null rows. So if a country has no medals, in a
sport it will appear as zero. For example:
select * from (
select noc, sport, athlete
from olympic_medal_winners
)
pivot xml (count(athlete) medal_winners for sport in (
select sport
from olympic_medal_winners
where sport like 'A%')
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 12/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
)
where rownum = 1;
NOC SPORT_XML
ALG <?xml version="1.0" encoding="UTF-8"?>
<PivotSet>
<item>
<column name="SPORT">Archery</column>
<column name="MEDAL_WINNERS">0</column>
</item>
<item>
<column name="SPORT">Artistic Gymnastics</column>
<column name="MEDAL_WINNERS">0</column>
</item>
<item>
<column name="SPORT">Athletics</column>
<column name="MEDAL_WINNERS">2</column>
</item>
</PivotSet>
If you want the XML to include all the sports you can use the any keyword instead:
The great part about this the results will automatically update if the query output
changes. So when Armchair Sitting becomes an Olympic sport the XML will
include this as an element :)
The terrible part is you get the output in XML! So you're going to have to parse
this to extract the values...
Custom Types
But there is another way! Anton Scheffer put together a solution using custom
types. This enables you to pivot the results of a query. For example:
select *
from table ( pivot (
'select noc,
medal,
count(distinct sport ||''#''|| event ||''#''|| gender ) count_medal
from olympic_medal_winners
group by noc, medal'
)
);
This is really cool. But it comes with notable parsing overheads. And if your query
creates a large number of columns the SQL it generates may be too large for the
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 13/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
variables it uses!
So far we've been working with the raw event results. But what if you've only got
the final medal table, like this:
And you want to write SQL converting the gold, silver and bronze columns into
rows?
Unpivot works in a similar way to pivot. You need to supply three things:
. The name of a new column that will store the values from the old columns
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 14/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
First name the two columns you want in the output. Then specify each pair of
columns that provide the values for the rows:
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 15/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
(silver_medals, silver_sports) as 'SILVER',
(bronze_medals, bronze_sports) as 'BRONZE'
))
fetch first 9 rows only;
Note the aliases for the column pairings. Without these medal column contains
the concatenation of the source column names. For example
GOLD_MEDALS_GOLD_SPORTS.
Unfortunately you can't split the values back out. So if all you have is the final
medal table, you can't go back and get the rows by sport, event and so on.
Unless your source columns are a comma separated list. Say, the names of the
athletes who won each medal!
In this case, you can unpivot to get the medals and athlete lists as rows:
You can now split the names into separate rows. There are several ways to do this.
Here I've used Stew Ashton's XQuery tokenizing solution:
with rws as (
select * from olympic_medal_tables
unpivot ((medal_count, athletes) for medal_colour in (
(gold_medals, gold_athletes) as 'GOLD',
(silver_medals, silver_athletes) as 'SILVER',
(bronze_medals, bronze_athletes) as 'BRONZE'
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 16/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
))
where medal_colour = 'GOLD'
and medal_count = 2
)
select noc, athlete
from rws, xmltable (
'if (contains($X,",")) then ora:tokenize($X,"\,") else $X'
passing athletes as X
columns athlete varchar2(4000) path '.'
)
order by 1, 2
fetch first 6 rows only;
NOC ATHLETE
BEL THIAM Nafissatou
BEL VAN AVERMAET Greg
DEN BLUME Pernille
DEN Denmark
GEO KHINCHEGASHVILI Vladimer
GEO TALAKHADZE Lasha
We've seen how to convert rows into columns. And columns back into rows. But
what if you want to both at the same time?
Your boss has asked you to switch it over, so countries are across the top and
sports down the side. Like so:
Aka a transpose.
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 17/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
But there isn't an in-built transpose function in SQL. So what's the solution?
Hmmm, that looks painful. For each extra column you want to transpose, you
need to add a sum to the pivot. Then a big list of countries in unpivot's sport list.
This is tedious.
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 18/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Much better :)
Adding more sports is simply a matter of adding a value to the unpivot. And for
more countries you just extend the pivot country list.
Conclusion
You've seen how the SQL pivot and unpivot operators can enable powerful data
transformations. Between these two you can swap your rows and columns over
however you like!
Just remember the three step process for each. For pivot it's the column(s) you're
pivoting on, the values defining the new columns and the functions giving the
values for these.
With unpivot you need the column which states the source of the values, what
these sources are and the list of columns you want to convert to rows.
All the examples so far are with a single table. Often you'll want to use pivots with
joins. You can pivot or unpivot the result of a join, for example:
Or
Scripts
If you want the scripts in this post, head over to LiveSQL. There you can find and
run the demos on a sample of the data. Or you can use this table:
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 19/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
athlete varchar2( 128 )
);
And load the medal winners into it from this CSV. You can then create the tables
for the unpivot and transpose examples from this like so:
Still stuck?
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 20/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Check the docs for more examples of how to use pivot and unpivot. Or you can
head over to Ask Tom where Connor and I can help you write your SQL. Just
remember to provide a test case!
Note: the examples use the "fetch first" syntax to get the first N rows. This is only
available in Oracle Database 12c and above. If you want to do top-N queries in
releases before this, you need to use the rownum trick.
Comments ( 29 )
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 21/22
3/7/2020 How to Convert Rows to Columns and Back Again with SQL (Aka PIVOT and UNPIVOT) | Oracle All Things SQL Blog
Recent Content
Site Map Legal Notices Terms of Use Privacy Cookie Preferences Ad Choices
Oracle Content Marketing Login
https://blogs.oracle.com/sql/how-to-convert-rows-to-columns-and-back-again-with-sql-aka-pivot-and-unpivot 22/22