Tuesday, January 28, 2014

pivot queries
A frequent requirement in SQL is to "pivot" a result set – that is, display rows as columns. Before 11G, the developer had to do the equivalent of jumping through hoops to pivot the data. In 11G, with the introduction of the new PIVOT syntax, the task of transposing rows to columns has become a lot more intuitive.
This post shows the use of PIVOT with an example and sample scripts.
Let’s take a table CONTACT that stores contact details of persons – landline numbers, mobile numbers, website URLs. A person can have multiple contacts of each type, and each contact type has a priority associated with it.
The requirement is to display the primary contact (priority = 1) of each type – Landline, Mobile, Website – as columns.
This is the entire data in the table CONTACT:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SQL> select person_key
  2         , contact_type_code
  3         , contact_detail
  4         , priority
  from contact
  order by person_key
  7         , contact_type_code
  8         , priority;
 
PERSON_KEY CONTACT_TY CONTACT_DETAIL     PRIORITY
---------- ---------- ------------------ --------
        12 LANDLINE   10234126                  1
        12 LANDLINE   10234124                  2
        12 MOBILE     81342122                  1
        12 WEBSITE    www.12mysite1.com         1
        12 WEBSITE    www.12mysite2.com         2
        12 WEBSITE    www.12mysite3.com         3
        14 MOBILE     6467433                   1
        14 MOBILE     5557433                   2
        14 WEBSITE    www.14site1.com           3
This is the data we are interested in:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SQL> select person_key
  2         , contact_type_code
  3         , contact_detail
  4         , priority
  from contact
  where priority = 1
  order by person_key
  8         , contact_type_code
  9         , priority;
 
PERSON_KEY CONTACT_TY CONTACT_DETAIL     PRIORITY
---------- ---------- ------------------ --------
        12 LANDLINE   10234126                  1
        12 MOBILE     81342122                  1
        12 WEBSITE    www.12mysite1.com         1
        14 MOBILE     6467433                   1
And this, the result set we want:
1
2
3
4
PERSON_KEY LANDLINE   MOBILE     WEBSITE
---------- ---------- ---------- -----------------
        14            6467433
        12 10234126   81342122   www.12mysite1.com

Pre 11G: Using DECODE

Before 11G, the popular solution to the pivot problem was with a combination of max and decode.
The SQL:
1
2
3
4
5
6
7
8
9
10
11
-- Pre 11G solution: rows to columns
select person_key
    , max(decode(contact_type_code
    , 'LANDLINE', contact_detail)) landline
    , max(decode(contact_type_code
    , 'MOBILE', contact_detail)) mobile
    , max(decode(contact_type_code
    , 'WEBSITE', contact_detail)) website
from contact
where priority = 1
group by person_key;
When run:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SQL> -- Pre 11G solution: rows to columns
SQL> select person_key
  2      , max(decode(contact_type_code
  3   , 'LANDLINE', contact_detail)) landline
  4      , max(decode(contact_type_code
  5   , 'MOBILE', contact_detail)) mobile
  6      , max(decode(contact_type_code
  7   , 'WEBSITE', contact_detail)) website
  from contact
  where priority = 1
 10  group by person_key;
 
PERSON_KEY LANDLINE   MOBILE     WEBSITE
---------- ---------- ---------- -----------------
        14            6467433
        12 10234126   81342122   www.12mysite1.com

In 11G: Using PIVOT

With the PIVOT syntax, the same query can be written in a more compact, readable form.
The SQL:
1
2
3
4
5
6
7
8
9
10
11
12
-- 11G solution (PIVOT): rows to columns
select *
from   (select person_key
             , contact_type_code
             , contact_detail
        from   contact
        where priority = 1)
pivot  (max(contact_detail)
       for (contact_type_code) in
       ('LANDLINE'
      , 'MOBILE'
      , 'WEBSITE'));
When run:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SQL> -- 11G solution (PIVOT): rows to columns
SQL> select *
  from   (select person_key
  3               , contact_type_code
  4               , contact_detail
  5          from   contact
  6          where priority = 1)
  7  pivot  (max(contact_detail)
  8         for (contact_type_code) in
  9         ('LANDLINE'
 10        , 'MOBILE'
 11        , 'WEBSITE'));
 
PERSON_KEY LANDLINE   MOBILE     WEBSITE
---------- ---------- ---------- -----------------
        14            6467433
        12 10234126   81342122   www.12mysite1.com

Notes and References

While PIVOT queries do look a lot more intuitive than their pre-11G alternatives, the restriction of having to hard-code the pivot values still remains. If a subquery is used instead of a fixed list in the pivot IN clause, Oracle raises an error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SQL> select *
  from   (select person_key
  3               , contact_type_code
  4               , contact_detail
  5          from   contact
  6          where priority = 1)
  7  pivot  (max(contact_detail)
  8         for (contact_type_code) in
  -- Subquery instead of list of values
 10         (select distinct
 11          contact_type_code
 12          from contact));
       (select distinct
        *
ERROR at line 10:
ORA-00936: missing expression
This restriction is relaxed in case the result set is generated in XML format with the "XML" keyword – this article gives details about its use. You will also find there more examples of pivoting with column aliases, pivoting on multiple columns, and performance analysis of pivot queries.

No comments:

Post a Comment