テクノロジー:SQLの基礎

分析関数の世界への扉を開く

著者:Melanie Caffrey
2013年3/4月

記事一覧へ戻る

リレーショナル・データベースとSQLの基本に関するシリーズ記事のパート10

このシリーズ記事のパート9 "合計、平均、その他のグループ化されたデータについて"(Oracle Magazine、2013年1/2月)では、よく使用されるSQL集計関数とGROUP BY句、HAVING句を紹介し、よりわかりやすい結果を得るために、それらを利用して単一行の結果セット・データやグループ化された結果セット・データを操作する方法を説明しました。集計関数について考察した後は必然的に、データの集計やその他の固有ビューを利用する高度なSQL操作の話題に移ることができます。この記事より3回にわたって、よく使用される分析関数および関連する句を紹介していきます。分析関数は、複数行に基づいて動作するだけではありません。少し挙げるだけでも、データのランキング作成、中間結果(ランニング・トータル)の計算、異なる期間での変化の識別といった操作も実行できます。これらの機能を利用すれば、レポート生成の目的で業務上の疑問に回答するような問合せを容易に作成できるようになります。

このシリーズ記事の例を試すには、Oracle Databaseインスタンスにアクセスする必要があります。必要に応じて、お使いのオペレーティング・システムに対応したOracle Databaseエディションをダウンロードし、インストールしてください。筆者がインストールをお勧めするエディションは、Oracle Database, Express Edition 11g Release 2です。このOracle Databaseソフトウェアをインストールする場合は、データベースの作成と構成が可能なインストール・オプションを選択してください。サンプルのユーザー・アカウントと関連する新しいスキーマを含む新しいデータベースが作成されます(SQL_101は、このシリーズ記事の例で使用するユーザー・アカウントです。また、データベースの表やその他のオブジェクトが作成されるスキーマでもあります)。インストール・プロセスの実行中にスキーマのパスワードを指定するように求められたら、SYSおよびSYSTEMのパスワードを入力して確認し、そのパスワードを覚えておいてください。

最後に、このデータベース・ソフトウェアをゼロからインストールした場合でも、既存のOracle Databaseインスタンスにアクセスする場合でも、SQLスクリプトをダウンロードして解凍し、実行して、この記事の例で必要となるSQL_101スキーマ用の表を作成します(このスクリプトをテキスト・エディタで開き、実行方法の説明を確認してください)。 

最終結果の向上

標準SQLを利用するだけでも、データに関するほとんどの疑問に回答できます。しかし、"従業員の給与の値を1行ずつ加算していった場合の中間結果は?"といった質問に回答するような純粋なSQL問合せは記述しづらく、長期的に見ればパフォーマンスが良くない場合もあります。分析関数はSQLを拡張する関数であり、そのような操作を高速化し、コーディングしやすくします。

リスト1の問合せは、SUM分析関数の使用例を示しています。この問合せの結果として、すべての従業員について、各従業員の給与の値とともに、給与の累計が表示されます。

コード・リスト1:すべての従業員について給与の累計を1行ずつ取得 


SQL> set feedback on
SQL> set lines 32000
SQL> select last_name, first_name, salary,
  2     SUM (salary)
  3    OVER (ORDER BY last_name, first_name) running_total
  4    from employee
  5   order by last_name, first_name;

LAST_NAME  FIRST_NAME                         SALARY RUNNING_TOTAL
—————————  ——————————— ————————————————————————————— ————————————— 
Dovichi    Lori
Eckhardt   Emily                              100000        100000
Friedli    Roger                               60000        160000
James      Betsy                               60000        220000
Jeffrey    Thomas                             300000        520000
Michaels   Matthew                             70000        590000
Newton     Donald                              80000        670000
Newton     Frances                             75000        745000
Wong       Theresa                             70000        815000
leblanc    mark                                65000        880000
peterson   michael                             90000        970000

11 rows selected. 

この累計は、問合せの次の行により得られます。 


SUM (salary)
  OVER (ORDER BY last_name, first_name) running_total  

分析関数の構造

分析関数の構文を習得すれば、分析関数を利用した効率的な問合せ処理のほぼ大半を終えたと言えます。リスト1における問合せの分析関連行の構文は次のとおりです。 


関数名( 列 | 式,列 | 式,...) 
OVER 
( ORDER BY句 )   

リスト1の関数名はSUMです。SUM関数の引数はSALARY列です(ただし、引数は式の場合もありあます)。OVER句は、この関数コールが(集計関数ではなく)分析関数であることを示すものです。ORDER BY句には、この分析関数の実行の"対象となる(OVER)"データを指定します。

スカラー副問合せについては、今後の記事で取り上げます。この記事では、リスト1の結果を得るために、スカラー副問合せという方法も利用できるということを知っておけば十分でしょう。ただし、スカラー副問合せはパフォーマンスが非常に悪く、リスト1の問合せの分析問合せ行と比較して、構文もより難しくなります。 

コード・リスト2:部門ごとに、累計の給与を1行ずつ取得 


SQL> select last_name, first_name, department_id, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY last_name, first_name) department_total

  4    from employee
  5  order by department_id, last_name, first_name;

LAST_NAME  FIRST_NAME     DEPARTMENT_ID     SALARY  DEPARTMENT_TOTAL
—————————  ——————————  ————————————————  ————————— —————————————————
Dovichi    Lori                      10
Eckhardt   Emily                     10     100000           100000
Friedli    Roger                     10      60000           160000
James      Betsy                     10      60000           220000
Michaels   Matthew                   10      70000           290000
Newton     Donald                    10      80000           370000
leblanc    mark                      20      65000            65000
peterson   michael                   20      90000           155000
Jeffrey    Thomas                    30     300000           300000
Wong       Theresa                   30      70000           370000
Newton     Frances                           75000            75000

11 rows selected. 

リスト2の問合せは、各部門内で従業員行のSALARY値の累計を出しています。PARTITION句によって、分析関数がそれぞれの部門グループ(つまりパーティション)に対して個別に適用されるようになります。見てのとおり、部門が10から20へ、20から30へ、さらに30から部門IDのない従業員レコードへと変わった後、累計がリセットされています。PARTITION句を含む分析関数の構文は、リスト1の例で使用した構文から、さらに次のように拡張されます。  


関数名( 引数,引数,… ) 
OVER 
( PARTITION句 ORDER BY句 ) 

別々のORDER

リスト1およびリスト2の問合せでは、返される行が従業員の姓、名でソートされます。これに対して、リスト3の問合せでは、分析関数計算に適した、少し異なる並べ替え基準が使用されます。

コード・リスト3:SALARY値に基づいた各行の計算 


SQL> select last_name, first_name, department_id, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY salary) department_total
  4    from employee
  5  order by department_id, salary, last_name, first_name;

LAST_NAME  FIRST_NAME     DEPARTMENT_ID     SALARY  DEPARTMENT_TOTAL
—————————  ———————————    —————————————  —————————  ————————————————
Friedli    Roger                     10      60000            120000
James      Betsy                     10      60000            120000
Michaels   Matthew                   10      70000            190000
Newton     Donald                    10      80000            270000
Eckhardt   Emily                     10     100000            370000
Dovichi    Lori                      10                       370000
leblanc    mark                      20      65000             65000
peterson   michael                   20      90000            155000
Wong       Theresa                   30      70000             70000
Jeffrey    Thomas                    30     300000            370000
Newton     Frances                           75000             75000

11 rows selected.  

リスト3の分析関数は、部門別の給与の合計値を計算し、各パーティションで給与の昇順にソートして、SALARY値がNULLの場合は最後に評価しています。見てのとおり、Lori Dovichiのレコード(SALARY値がNULLである唯一のレコード)では、DEPARTMENT_TOTALの値が、同じ部門内でSALARY値が最大であるレコード(Emily Eckhardt)と同じになっています。

分析関数のORDER BY句は、分析関数を含む問合せ全体のORDER BY句とは独立して動作します。これら2種類のORDER BY句は、同じ列または式のリストを同じ順で使用しない限り、ほとんど、あるいはまったく関係しません。たとえば、リスト4では、返されるデータは(リスト1やリスト2の結果セットと同様に)部門/姓/名の順で表示されますが、DEPARTMENT_TOTAL式で返される値は、リスト3で返されるその値と一致しています。さらに、リスト3とリスト4ではBetsy JamesとLori Dovichiの順序が異なりますが、それぞれの部門合計の計算値は同じです。

コード・リスト4:リスト3の問合せとは異なるデータ・ソート方法 


SQL> select last_name, first_name, department_id, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY salary) department_total
  4    from employee
  5  order by department_id, last_name, first_name;

LAST_NAME  FIRST_NAME     DEPARTMENT_ID     SALARY  DEPARTMENT_TOTAL
—————————  ———————————    —————————————  —————————  ————————————————
Dovichi    Lori                      10                      370000
Eckhardt   Emily                     10     100000           370000
Friedli    Roger                     10      60000           120000
James      Betsy                     10      60000           120000
Michaels   Matthew                   10      70000           190000
Newton     Donald                    10      80000           270000
leblanc    mark                      20      65000            65000
peterson   michael                   20      90000           155000
Jeffrey    Thomas                    30     300000           370000
Wong       Theresa                   30      70000            70000
Newton     Frances                           75000            75000

11 rows selected.   

ウィンドウの選択

分析関数には、ウィンドウ句を含めることもできます(含めなくてもかまいません)。ウィンドウ句とは、分析関数計算で評価される特定のパーティション内で、行のグループ(ウィンドウ)を定義するための一連のパラメータまたはキーワードです。リスト1の問合せでは、デフォルトのウィンドウ句が使用されます。これは、ORDER BY句が使用されているためです。ORDER BY句では、その他のウィンドウ句のパラメータがない場合に、デフォルトのウィンドウ句であるRANGE UNBOUNDED PRECEDINGが効果的に追加されます。この句は、"現在のパーティション内の現在の行およびそれ以前の行を、計算で使用する行とする"という意味になります。ORDER BY句がPARTITION句とともに使用されない場合、分析関数で使用される行セット全体が、デフォルトでの現在のパーティションとなります。

リスト3とリスト4の問合せにはPARTITION句がありますが、ウィンドウ句パラメータは指定されていません。計算結果では、Betsy JamesとRoger FriedliのDEPARTMENT_TOTALの値が同一です。ウィンドウ句パラメータがないと、問合せの分析関数がそのパーティション内で特定の列または式に基づいてデータを並べ替え、2つ以上の行についてその値が同じ場合に、分析関数がそれぞれの行に適用されるため同じ結果が返されます。分析関数で、評価すべき順序を確定できないためです。

リスト5の問合せでは、ROWS 2 PRECEDINGウィンドウ句を使用することで、現在の行のSALARY値を、直前の2行のSALARY値のみと合算しています。Matthew Michaelsの直前の従業員であるBetsy JamesのDEPARTMENT_TOTAL値は220000ですが、Matthew MichaelsのDEPARTMENT_TOTAL値は190000です。これは、Matthew MichaelsのSALARY値の70000が、直前の2行(Betsy JamesとRoger Friedliの行)のSALARY値のみと合算されているからです。

コード・リスト5:ROWSウィンドウ句の追加 


SQL> select last_name, first_name, department_id, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY last_name, first_name
  4          ROWS 2 PRECEDING) department_total
  5    from employee
  6   order by department_id, last_name, first_name;

LAST_NAME  FIRST_NAME     DEPARTMENT_ID     SALARY  DEPARTMENT_TOTAL
—————————  ———————————    —————————————  —————————  ————————————————
Dovichi    Lori                      10
Eckhardt   Emily                     10     100000           100000
Friedli    Roger                     10      60000           160000
James      Betsy                     10      60000           220000
Michaels   Matthew                   10      70000           190000
Newton     Donald                    10      80000           210000
leblanc    mark                      20      65000            65000
peterson   michael                   20      90000           155000
Jeffrey    Thomas                    30     300000           300000
Wong       Theresa                   30      70000           370000
Newton     Frances                           75000            75000

11 rows selected.   

分析関数に対してパラメータ付きのウィンドウ句を追加する場合の構文は次のようになります。 


関数名( 引数,引数,… ) 
OVER 
( PARTITION句 ORDER BY句 ウィンドウ句)  

データへの複数のウィンドウの適用

ウィンドウ句は、渡すパラメータに応じて、データのスライド・ビューまたはアンカー・ビューのいずれかを生成します。ORDER BY句のみを使用した問合せ(リスト1、2、3、4の問合せなど)は、データのアンカー・ビューを生成します。このビューはパーティションの1行目(最上部)から始まり、現在処理中の行で終わります。リスト5の問合せ結果は、データのスライド・ビューです。各行のDEPARTMENT_TOTAL値が、各パーティション内のデータのソート(並べ替え)方法によって変化するからです。

リスト5は、ウィンドウ句へのパラメータ入力としてROWS句を使用した例です。また、RANGE句を使用してデータのスライド・ビューを作成することもできます。ROWS句とは異なり、RANGEウィンドウ句は、データ型が数値または日付である列や式を含むORDER BY句でのみ使用できます。このようなデータ型の要件があるのは、この句が、現在の行の特定範囲内にあるすべての行に基づいて動作するからです。各パーティション内でデータ並べ替えの基準となる列や式の値が、現在の行を始点とした(数値単位または日付単位での)指定範囲内に収まります。

コード・リスト6:雇用日を基準としたパーティションのソートとRANGEウィンドウ句の使用 


SQL> select last_name, first_name, department_id, hire_date, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY hire_date
  4          RANGE 90 PRECEDING) department_total
  5   from employee
  6   order by department_id, hire_date;

LAST_NAME  FIRST_NAME      DEPARTMENT_ID HIRE_DATE   SALARY DEPARTMENT_TOTAL
—————————  —————————— —————————————————— —————————   —————— ———————————————— 

Eckhardt   Emily                      10 07-JUL-04   100000           100000
Newton     Donald                     10 24-SEP-06    80000            80000
James      Betsy                      10 16-MAY-07    60000           190000
Friedli    Roger                      10 16-MAY-07    60000           190000
Michaels   Matthew                    10 16-MAY-07    70000           190000
Dovichi    Lori                       10 07-JUL-11
peterson   michael                    20 03-NOV-08    90000            90000
leblanc    mark                       20 06-MAR-09    65000            65000
Jeffrey    Thomas                     30 27-FEB-10   300000           300000
Wong       Theresa                    30 27-FEB-10    70000           370000
Newton     Frances                       14-SEP-05    75000            75000

11 rows selected. 

リスト6の問合せでは、RANGE句の動作例を示しています。この問合せのパーティションはHIRE_DATEでソートされます。この後、この問合せでは次のウィンドウ句を指定しています。 



RANGE 90 PRECEDING     

この行は、"現在の行のSALARY値を、現在の行のHIRE_DATE値からさかのぼって90日以内のHIRE_DATE値を持つ、前にあるすべての行のSALARY値と合算する"という意味になります。部門10では、DEPARTMENT_TOTAL値とSALARY値が異なる行が3行しかありません。この3行の従業員はみな同じ日に雇用されており、SALARY合算に必要となる日付値の範囲に収まっています。

また、部門30では、2人の従業員が同じ日に雇用されていますが、DEPARTMENT_TOTAL値とSALARY値が異なるのは1行だけです。この原因は、RANGE句のPRECEDINGキーワードにあります。このキーワードは、"現在の行よりも前にある行を探してから、ソート対象のHIRE_DATE単位が現在の行のHIRE_DATEの範囲内に収まるかを判断する"という意味になります。部門30にThomas Jeffreyよりも前の行はないため、この従業員のDEPARTMENT_TOTAL値の結果は変更されず、表示されるSALARY値と同じになるのです。

リスト7の問合せでは、データ型が日付または数値である列や式のみを使用することがいかに重要かを示しています。この問合せでは、従業員の姓と名で各パーティションをソートしようとしています。RANGEウィンドウ句で、適切な値の範囲を判断できるのは、数値または日付の範囲を使用する場合に限られます。テキストや文字列の範囲は使用できません。そのため、この問合せにより適切な範囲を判断できず、エラーが発生します。

次のステップ

 その他の記事 SQLの基礎、パート1~9  

リレーショナル・データベースの

設計と概念に関する詳細の確認

Oracle Database Concepts 11g Release 2 (11.2)

Oracle Database SQL言語リファレンス11g リリース1(11.1)

Oracle Databaseデータ・ウェアハウス・ガイド11gリリース2(11.2)

Oracle SQL Developerユーザーズ・ガイド リリース3.1

ダウンロード この記事のサンプル・スクリプト

コード・リスト7:正しくないデータ型を使用するRANGEウィンドウ句 


SQL> select last_name, first_name, department_id, hire_date, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY last_name, first_name
  4         RANGE 90 PRECEDING) department_total
  5  from employee
  6  order by department_id, hire_date;
   SUM (salary)
   *
ERROR at line 2:
ORA-30486: invalid window aggregation group in the window specification

また、問合せの分析関数でRANGEウィンドウ句を使用している場合、ORDER BY句には列または式を1つのみ使用できます。つまり、範囲は1次元的です。これらの制約はROWSウィンドウ句には適用されません。ROWS句は任意のデータ型に適用でき、ORDER BY句で使用できる列や式が1つに制限されることもありません。

視点の絞り込み

もっとも基本的な形式のウィンドウは、相互に排他的な3種類の方法のいずれかで指定できます。表1に、ウィンドウ句のROWSまたはRANGEに渡すことのできるパラメータのタイプを示します。

ウィンドウ句のパラメータ 説明
現在の行 ウィンドウは現在処理中の行で始まり、その行で終わります。
UNBOUNDED PRECEDING ウィンドウは現在のパーティションの1行目で始まり、現在処理中の行で終わります。
数値式 PRECEDING ROWS句 – ウィンドウは現在の行の前にある数値式の行で始まり、現在処理中の行で終わります。
RANGE句 – ウィンドウは、ORDER BY値が現在の行よりも小さい(または現在の行よりも前にある)数値式の行で始まり、現在処理中の行で終わります。

表1:ウィンドウ句のパラメータ

この記事でこれまで紹介したウィンドウはすべて、現在の行で終わり、計算時には前の行または範囲の値を使用するものです。一方、BETWEEN演算子を使用して、結果セット内の中間に現在の行が収まるようなウィンドウを指定することもできます。リスト8の問合せでは、ROWS句またはRANGE句で、ウィンドウが前の行の値で始まり現在処理中の行で終わることを指定するのに加えて、FOLLOWINGパラメータも使用して、現在処理中の行の後の行を探し、それらの行の値に基づいて評価できます。

コード・リスト8:BETWEENパラメータとFOLLOWINGパラメータを使用したRANGEウィンドウ句を含む問合せ 


SQL> select last_name, first_name, department_id, hire_date, salary,
  2     SUM (salary)
  3    OVER (PARTITION BY department_id ORDER BY hire_date
  4          RANGE BETWEEN 365 PRECEDING AND 365 FOLLOWING) department_total
  5  from employee
  6  order by department_id, hire_date;

LAST_NAME FIRST_NAME       DEPARTMENT_ID  HIRE_DATE   SALARY DEPARTMENT_TOTAL
————————— ———————————— ————————————————— —————————— ———————— ———————————————— 

Eckhardt   Emily                      10 07-JUL-04   100000           100000
Newton     Donald                     10 24-SEP-06    80000           270000
James      Betsy                      10 16-MAY-07    60000           270000
Friedli    Roger                      10 16-MAY-07    60000           270000
Michaels   Matthew                    10 16-MAY-07    70000           270000
Dovichi    Lori                       10 07-JUL-11
peterson   michael                    20 03-NOV-08    90000           155000
leblanc    mark                       20 06-MAR-09    65000           155000
Jeffrey    Thomas                     30 27-FEB-10   300000           370000
Wong       Theresa                    30 27-FEB-10    70000           370000
Newton     Frances                       14-SEP-05    75000            75000

11 rows selected.  

結論

分析関数は、他の方法では複雑でおそらくはパフォーマンスの悪いSQLが必要になるようなデータについて、疑問に対する回答を得られる優れた方法です。どの分析関数を使用すべきか、さらにはユーザーにとってもっともわかりやすい結果が得られるデータのレポート・ビューを生成するのはどのウィンドウ句(使用する場合)であるかは、各ユーザーのレポート・ニーズによって変わります。この記事では、よく使用される分析関数(SUM)やPARTITION句とOVER句、ROWSとRANGEの各ウィンドウ句に関する使用例と、いくつかのよく使用される基本的なウィンドウ句のパラメータの指定について取り上げました。このSQLの基礎に関する次回の記事では、引き続き分析関数について説明します。Oracle分析関数を使用して収集できるデータに関して詳しくは、bit.ly/yWtbz1 およびbit.ly/R4cZyqを確認してください。

Melanie Caffreyの顔写真

Melanie Caffreyはオラクルの上級開発マネージャーです。Expert PL/SQL Practices for Oracle Developers and DBAs(Apress、2011年)およびExpert Oracle Practices: Oracle Database Administration from the Oak Table(Apress、2010年)の共著者でもあります。