Search notes:

Oracle SQL: SELECT with PIVOT clause

pivot [xml] ( aggregate-function(expr), … ) for (column…) in (…)

Selected columns and implicit group by

The pivot clause does not require an explicit group by clause to aggregate the values of the expressions in the specified aggregate-functions. Rather, the pivot clause implicitly groups on all columns that are not explicitly mentioned in the pivot clause.
The following table and select statements try to elaborate on that.
create table tq84_pivot_test (
   id      number,
   col_1   varchar2(5),
   col_2   varchar2(5),
   col_3   varchar2(5),
   val_1   number,
   val_2   varchar2(5)
);

begin
   insert into tq84_pivot_test values (1, 'A', 'X', 'p', 8, 'one'  );
   insert into tq84_pivot_test values (2, 'A', 'X', 'p', 9, 'two'  );
   insert into tq84_pivot_test values (3, 'A', 'X', 'q', 2, 'three');  
   insert into tq84_pivot_test values (4, 'B', 'X', 'p', 7, 'four' );
   insert into tq84_pivot_test values (5, 'B', 'X', 'q', 5, 'five' );
   insert into tq84_pivot_test values (6, 'B', 'Y', 'r', 4, 'six'  );
   insert into tq84_pivot_test values (7, 'B', 'Y', 'q', 3, 'seven');
   insert into tq84_pivot_test values (8, 'C', 'Z', 'q', 6, 'eight');
   insert into tq84_pivot_test values (7, 'A', 'Y', 'q', 1, 'seven');
   commit;
end;
/
In the following query, the pivot clause refers to the columns val_1 and col_1. These columns don't appear in the result set as ordinary columns, rather the in clause specifies the names of the additional columns, the aggregate function the column that is aggregated: The
select
   *
from
   tq84_pivot_test
   pivot (
      max(val_1) for
      col_1 in ('A', 'B')
   )
;
--
--         ID COL_2 COL_3 VAL_2        'A'        'B'
-- ---------- ----- ----- ----- ---------- ----------
--          1 X     p     one            8           
--          2 X     p     two            9           
--          3 X     q     three          2           
--          4 X     p     four                      7
--          5 X     q     five                      5
--          6 Y     r     six                       4
--          7 Y     q     seven          1          3
--          8 Z     q     eight                      
The previous select statement is a bit meaningless because it groups on all available columns. Therefore, in most cases, it is advisable to select the columns which are grouped by in a subquery:
select
   *
from (
--
-- This subquery explicitely selects the values
-- we want to have in the result. I. e.: the values for the columns ID and
-- VAL_2 are explicitely excluded:
--
   select
      col_1, col_2, col_3,
      val_1
   from
      tq84_pivot_test
)
pivot ( max(val_1) for col_1 in ('A', 'B'))
;
--
-- COL_2 COL_3        'A'        'B'
-- ----- ----- ---------- ----------
-- X     p              9          7
-- X     q              2          5
-- Y     r                         4
-- Y     q              1          3
-- Z     q                          
Finally, it is also possible to specify combination of «column values» for the additional columns.
By default, they have a rather ugly name with apostrophes in them. They can, however, be given more beautiful or meaningful names using the as … clause:
select
   *
from (
   select
      col_1, col_2, col_3,
      val_1
   from
      tq84_pivot_test
)
pivot (
   max(val_1) for
     (col_1, col_2) in (
       ('A', 'X') as ax, -- Make column name more pleasing
       ('B', 'Y'),
       ('C', 'Z')
    )
);
--
-- COL_3         AX    'B'_'Y'    'C'_'Z'
-- ----- ---------- ---------- ----------
-- p              9                      
-- q              2          3          6
-- r                         4        
Cleaning up:
drop table tq84_pivot_test;

Other examples

Sum of sale per month and item

A table that stores the date (dt) when an item (identified by txt) was sold. The price is kept in value:
create table tq84_sale (
   dt          date,
   txt         varchar2(10),
   value       number(5,2)
);
Fill some test data:
begin
                                                                 --     M o n t h      
                                                                 --                    
                                                                 --   07   08   09     
                                                                 -- ---------------    

insert into tq84_sale values (date '2000-08-10',  'Item 1', 10); --        10      
insert into tq84_sale values (date '2000-09-12',  'Item 1', 12); --             12
insert into tq84_sale values (date '2000-09-15',  'Item 1', 14); --             14
insert into tq84_sale values (date '2000-09-16',  'Item 1',  2); --              2
                                                                 --   --   --   --     
--                                            Sums for Item 1    --        10   28     
                                                                 --   ==   ==   ==     


insert into tq84_sale values (date '2000-07-02',  'Item 2', 11); --   11             
insert into tq84_sale values (date '2000-07-05',  'Item 2',  7); --    7
insert into tq84_sale values (date '2000-08-22',  'Item 2',  8); --         8
--                                                                    --   --   -- 
--                                             Sums for Item 2  :     18    8
--                                                                    ==   ==   == 

insert into tq84_sale values (date '2000-08-22',  'Item 3',  6); --         6        
--                                                                    --   --   --
--                                             Sums for Item 2  :           6
--                                                                    ==   ==   ==
end;
/
Query the data for three select months:
with translate_dt_to_month as (
   select
      to_char(dt, 'MM') month,
      txt,
      value
   from
      tq84_sale
)
select
   *
from
  translate_dt_to_month
  pivot (
     sum(value) for
     month in ('07' jul, '08' aug, '09' sep)
  )
order by
   txt;
--
-- TXT               JUL        AUG        SEP
-- ---------- ---------- ---------- ----------
-- Item 1                        10         28
-- Item 2             18          8           
-- Item 3                         6       
Cleaning up:
drop table tq84_sale

Summing two values

This example is similar to the previous one, but is sums two values per period and item:
create table tq84_pivot (
  dt          date,
  txt         varchar2(10),
  value1      number(5,2),
  value2      number(5,2)
);


insert into tq84_pivot values (date '2000-08-10',  'Item 1',  10,  9);
insert into tq84_pivot values (date '2000-09-12',  'Item 1',  12, 11);
insert into tq84_pivot values (date '2000-09-15',  'Item 1',  14, 13);
insert into tq84_pivot values (date '2000-09-16',  'Item 1',   2,  1);

insert into tq84_pivot values (date '2000-07-02',  'Item 2',  11, 10);
insert into tq84_pivot values (date '2000-07-05',  'Item 2',   7,  6);
insert into tq84_pivot values (date '2000-08-22',  'Item 2',   8,  7);

insert into tq84_pivot values (date '2000-08-22',  'Item 3',   6,  5);
insert into tq84_pivot values (date '2000-09-23',  'Item 3',1000,  5);

with translate_dt_to_month as (
  select to_char(dt, 'MM') month, txt, value1, value2 from tq84_pivot
)
select * from translate_dt_to_month
  --
  pivot ( sum(value1) sum_1, sum(value2) sum_2 for month in ('07', '08', '09') )
  --
order by txt;
--
-- TXT        '07'_SUM_1 '07'_SUM_2 '08'_SUM_1 '08'_SUM_2 '09'_SUM_1 '09'_SUM_2
-- ---------- ---------- ---------- ---------- ---------- ---------- ----------
-- Item 1                                   10          9         28         25
-- Item 2             18         16          8          7
-- Item 3                                    6          5

drop table tq84_pivot;

«Better» aliases for generated pivot columns.

create table tq84_pivot_03 (
  nm number,
  tx varchar2(2)
);

insert into tq84_pivot_03 values ( 1, '/?'   );
insert into tq84_pivot_03 values ( 3, '/?'   );

insert into tq84_pivot_03 values ( 7, '  '   );
insert into tq84_pivot_03 values ( 8, '  '   );
insert into tq84_pivot_03 values ( 2, '  '   );

insert into tq84_pivot_03 values (66, chr(10));
insert into tq84_pivot_03 values (59, chr(10));
insert into tq84_pivot_03 values (31, chr(10));

select "'/?'", "'  '", "'
'"
from tq84_pivot_03
  pivot (max(nm)  for tx in ('/?', '  ', '
'));


select
  slash_question,
  space_space,
  new_line
from
  tq84_pivot_03
pivot (max(nm)  for tx in (
  '/?' as slash_question,
  '  ' as space_space, '
'      as new_line
  )
);

drop table tq84_pivot_03 purge;

Two aggregates

create table tq84_two_aggregates (
       item    varchar2(20)  not null,
       region  varchar2( 5)  not null check (region in ('North', 'South', 'West', 'East')),
       price   number,
       sold    number,
       foo     varchar2(2)
);



insert into tq84_two_aggregates values ('Apple', 'North',    0.85,   1094, '?' );
insert into tq84_two_aggregates values ('Apple', 'South',    0.79,     88, '!!');
insert into tq84_two_aggregates values ('Apple', 'West' ,    0.81,    203, '%' );

insert into tq84_two_aggregates values ('Pear' , 'North',    1.05,   4593, '#-');
insert into tq84_two_aggregates values ('Pear' , 'South',    0.98,   5010, '/' );
insert into tq84_two_aggregates values ('Pear' , 'East' ,    1.03,    912, '\\');

insert into tq84_two_aggregates values ('Mango', 'South',    2.44,    417, '_/');

-- update tq84_two_aggregates set foo = '1';


select * from (
  select item, price, sold, /* foo,*/ region from tq84_two_aggregates
)
pivot (
  sum(sold)   as sold ,
  max(price)  as price
  --
  for region in ('North' as north_, 'South' as south_, 'West' as west_, 'East' as east_)
)
;

drop table tq84_two_aggregates purge;

See also

The unpivot clause.
The HASH (GROUP BY PIVOT) plan operator.
DuckDB's implemenation of PIVOT deteremines the number of returned columns dynamically at execution time.

Index