![]() | Oracle Database 11g: 著者:Arup Nanda |
簡単なSQLを使用して任意のリレーショナル表からスプレッドシート型のクロス集計レポートに情報を表示し、クロス集計表から任意のデータをリレーショナル表に格納します。
ご存知のとおり、リレーショナル表は当然ながら表形式です。つまり、列/値ペアで提示されます。CUSTOMERSという名前の表について検討してみましょう。
SQL> desc customers Name Null? Type ----------------------------------------- -------- --------------------------- CUST_ID NUMBER(10) CUST_NAME VARCHAR2(20) STATE_CODE VARCHAR2(2) TIMES_PURCHASED NUMBER(3)
select cust_id, state_code, times_purchased from customers order by cust_id;
CUST_ID STATE_CODE TIMES_PURCHASED
------- ---------- ---------------
1 CT 1
2 NY 10
3 NJ 2
4 NY 4
...
以降、省略
データが、値の行としてどのように表現されるかを見てみましょう。各顧客について、顧客の住所がある州と、店舗からの購入回数がレコードとして表示されます。顧客が店舗から購入した品物が増えると、列times_purchasedが更新されます。 では、購入頻度を州別にレポートする場合、つまり、購入回数が1回のみ、2回、3回、などの顧客の数を州別にカウントする場合を考えてみましょう。通常のSQLで、次の文を実行できます。
select state_code, times_purchased, count(1) cnt from customers group by state_code, times_purchased;
ST TIMES_PURCHASED CNT
-- --------------- ----------
CT 0 90
CT 1 165
CT 2 179
CT 3 173
CT 4 173
CT 5 152
...
以降、省略
必要なのはこの情報ですが、これでは解読するのが少し困難です。この同じデータを見やすく表現するには、クロス集計レポートを使用する方法があります。クロス集計レポートでは、スプレッドシートと同じように、データを縦方向、州を横方向に配列できます。
Times_purchased
CT NY NJ ...
以降、省略
1 0 1 0 ...
2 23 119 37 ...
3 17 45 1 ...
...
以降、省略
11gより前のOracle Databaseでこれを実行する場合は、各値についてなんらかのDECODE関数を使用し、個別の値を個別の列として書き込むという方法を取りました。ですが、この手法は分かりやすいとはとても言えません。 幸い、現在はピボットという優れた新機能があり、pivotという相応な名前の新しい演算子を使用して任意の問合せをクロス集計形式で表示できます。この問合せの記述方法は次のとおりです。
select * from (
select times_purchased, state_code
from customers t
)
pivot
(
count(state_code)
for state_code in ('NY','CT','NJ','FL','MO')
)
order by times_purchased
/
出力結果は次のようになります。
.TIMES_PURCHASED 'NY' 'CT' 'NJ' 'FL' 'MO'
--------------- ---------- ---------- ---------- ---------- ----------
0 16601 90 0 0 0
1 33048 165 0 0 0
2 33151 179 0 0 0
3 32978 173 0 0 0
4 33109 173 0 1 0
... 以下、省略

図1:従来の表形式での表示
クロス集計レポートでは、図2に示すように、Times Purchased列をヘッダー行に変える必要があります。列を反時計回りに90度回転させてヘッダー行にしたかのように、列が行になります。この図形的な回転を行うにはピボット・ポイントが必要ですが、この例ではうまい具合にcount(state_code)式がピボット・ポイントです。
図2:ピボット演算後の表示
この式は、次のように問合せの構文に含める必要があります。
...
pivot
(
count(state_code)
for state_code in ('NY','CT','NJ','FL','MO')
)
...
出力のヘッダー行を見てみましょう。
.TIMES_PURCHASED 'NY' 'CT' 'NJ' 'FL' 'MO'
--------------- ---------- ---------- ---------- ---------- ----------
select * from (
select times_purchased as "Puchase Frequency", state_code
from customers t
)
pivot
(
count(state_code)
for state_code in ('NY' as "New York",'CT' "Connecticut",'NJ' "New Jersey",'FL' "Florida",'MO' as "Missouri")
)
order by 1
/
Puchase Frequency New York Connecticut New Jersey Florida Missouri
----------------- ---------- ----------- ---------- ---------- ----------
0 16601 90 0 0 0
1 33048 165 0 0 0
2 33151 179 0 0 0
3 32978 173 0 0 0
4 33109 173 0 1 0
...
以降、省略
FOR句では値の別名を指定することができ、別名が列ヘッダーになります。 物質に反物質があるように、ピボットにも"反ピボット"があるべきですよね。
冗談はさておき、ピボット演算を反転させる演算の必要は本当にあるのです。下に示すクロス集計レポートを表示するスプレッドシートがあるとします。
| Purchase Frequency | New York | Connecticut | New Jersey | Florida | Missouri |
| 0 | 12 | 11 | 1 | 0 | 0 |
| 1 | 900 | 14 | 22 | 98 | 78 |
| 2 | 866 | 78 | 13 | 3 | 9 |
| ... | . |
SQL> desc customers Name Null? Type ----------------------------------------- -------- --------------------------- CUST_ID NUMBER(10) CUST_NAME VARCHAR2(20) STATE_CODE VARCHAR2(2) TIMES_PURCHASED NUMBER(3)
分かりやすくするために、例を使って説明します。まず、pivot演算を使用してクロス集計表を作成しましょう。
1 create table cust_matrix
2 as
3 select * from (
4 select times_purchased as "Puchase Frequency", state_code
5 from customers t
6 )
7 pivot
8 (
9 count(state_code)
10 for state_code in ('NY' as "New York",'CT' "Conn",'NJ' "New Jersey",'FL' "Florida",
'MO' as "Missouri")
11 )
12* order by 1
SQL> select * from cust_matrix
2 /
Puchase Frequency New York Conn New Jersey Florida Missouri
----------------- ---------- ---------- ---------- ---------- ---------
1 33048 165 0 0 0
2 33151 179 0 0 0
3 32978 173 0 0 0
4 33109 173 0 1 0
... 以下、省略
SQL> desc cust_matrix Name Null? Type ----------------------------------------- -------- --------------------------- Puchase Frequency NUMBER(3) New York NUMBER Conn NUMBER New Jersey NUMBER Florida NUMBER Missouri NUMBER
select *
from cust_matrix
unpivot
(
state_counts
for state_code in ("New York","Conn","New Jersey","Florida","Missouri")
)
order by "Puchase Frequency", state_code
/
Puchase Frequency STATE_CODE STATE_COUNTS
----------------- ---------- ------------
1 Conn 165
1 Florida 0
1 Missouri 0
1 New Jersey 0
1 New York 33048
2 Conn 179
2 Florida 0
2 Missouri 0
...
以降、省略
それぞれの列名がどのようにしてSTATE_CODE列の値になったかを見てみましょう。state_codeが列名であると、Oracleにはどうして分かったのでしょうか。問合せに含まれる次の句がOracleに教えたのです。
for state_code in ("New York","Conn","New Jersey","Florida","Missouri")
Purchase Frequency New York Conn New Jersey Florida Missouri
----------------- ---------- ---------- ---------- ---------- - ---------
1 33048 165 0 0 0
unpivotはpivotの逆の操作だと考えることもできますが、pivotの結果が当然unpivotで戻せると思わないでください。たとえば、上の例では表CUSTOMERSに対してpivot演算を使用して、新しい表CUST_MATRIXを作成しました。その後、表CUST_MATRIXに対してunpivotを使用しましたが、元の表CUSTOMERSの詳細は元に戻りませんでした。その代わり、リレーショナル表にロードできるように、クロス集計レポートが異なる方法で表示されました。つまり、unpivotはpivotの実行結果を元に戻すためのものではありません。ピボット表を作成した後に元の表を削除する前に、この事実を十分考慮する必要があります。
unpivotの使用例として非常に興味深いものの中には、前述した例のような普通の強力なデータ操作の域を超えているものもあります。Amis TechnologiesでOracle ACE Directorを務めるLucas Jellemaは、テストを目的として特定のデータの行を生成する方法を紹介しています。ここでは、彼のオリジナル・コードを少し変更した形式を使用して、英語のアルファベットの母音を生成します。
select value
from
(
(
select
'a' v1,
'e' v2,
'i' v3,
'o' v4,
'u' v5
from dual
)
unpivot
(
value
for value_type in
(v1,v2,v3,v4,v5)
)
)
/
V - a e i o u
上の例では、有効なstate_codeを次のように指定する必要がありました。
for state_code in ('NY','CT','NJ','FL','MO')
pivot演算には、XMLという句があります。この句を使用するとピボット演算の出力をXMLとして作成でき、その場合はリテラル値の代わりに特殊な句、ANYを指定できます。次に例を示します。
select * from (
select times_purchased as "Purchase Frequency", state_code
from customers t
)
pivot xml
(
count(state_code)
for state_code in (any)
)
order by 1
/
出力はCLOBとして返されるので、LONGSIZEが大きい値に設定されていることを確認してから問合せを実行してください。 SQL> set long 99999
Purchase Frequency STATE_CODE_XML
------------------ --------------------------------------------------
1 <PivotSet><item><column name = "STATE_CODE">CT</co
lumn><column name = "COUNT(STATE_CODE)">165</colum
n></item><item><column name = "STATE_CODE">NY</col
umn><column name = "COUNT(STATE_CODE)">33048</colu
mn></item></PivotSet>
2 <PivotSet><item><column name = "STATE_CODE">CT</co
lumn><column name = "COUNT(STATE_CODE)">179</colum
n></item><item><column name = "STATE_CODE">NY</col
umn><column name = "COUNT(STATE_CODE)">33151</colu
mn></item></PivotSet>
... 以下、省略
ANY句の他に副問合せも記述できます。希望する州のリストがあり、それらの州の行だけを選択する必要があるとします。preferred_statesという名前の新しい表に、希望する州を配置します。
SQL> create table preferred_states
2 (
3 state_code varchar2(2)
4 )
5 /
Table created.
SQL> insert into preferred_states values ('FL')
2> /
1 row created.
SQL> commit;
Commit complete.
select * from ( select times_purchased as "Puchase Frequency", state_code from customers t ) pivot xml ( count(state_code) for state_code in (select state_code from preferred_states) ) order by 1 /
for state_code in (select distinct state_code from customers)
pivotは非常に重要かつ実用的な機能をSQL言語にもたらしました。大量のDECODE関数を使用した複雑で分かりにくいコードを記述する代わりに、ピボット機能を使用して任意のリレーショナル表のクロス集計レポートを作成できます。同様に、unpivot演算を使用すれば、任意のクロス集計レポートを通常のリレーショナル表として格納するために変換することができます。 pivotからの出力は、通常のテキストまたはXMLで作成できます。XMLで作成する場合は、pivot演算で検索する必要のある値のドメインを指定する必要がありません。
ピボット演算およびアンピボット演算について詳しくは、Oracle Database 11g SQL言語リファレンスを参照してください。
記事一覧へ戻る