SQLアタマ養成講座

第5回 SQL流行間比較(1) はじめに

この記事を読むのに必要な時間:およそ 4 分

この自己結合の場合も,考え方は相関サブクエリの場合とまったく同じです。試しに,集約抜きにしてヒラで結果を得てみましょうリスト4図2)⁠

リスト4 説明用:非集約の状態で取ると

SELECT LS0.sample_date AS cur_date,
       LS1.sample_date AS latest
  FROM LoadSample LS0
           LEFT OUTER JOIN LoadSample LS1
             ON LS1.sample_date < LS0.sample_date;

図2 リスト4の実行結果

cur_date   latest
-------- --------
08-02-01             ← S0:2月1日より小さい日付は
                                     1つもないので,0個
08-02-02 08-02-01    ← S1:2月2日より小さい日付は1個

08-02-05 08-02-01    ← S2:2月5日より小さい日付は2個
08-02-05 08-02-02

08-02-07 08-02-01    ← S3:2月7日より小さい日付は3個
08-02-07 08-02-02
08-02-07 08-02-05

08-02-08 08-02-01    ← S4:2月8日より小さい日付は4個
08-02-08 08-02-02
08-02-08 08-02-05
08-02-08 08-02-07

08-02-12 08-02-01    ← S5:2月12日より小さい日付は5個
08-02-12 08-02-02
08-02-12 08-02-05
08-02-12 08-02-07
08-02-12 08-02-08

このように,カレントの日付であるcur_dateに対して,それぞれより小さい日付の集合が得られることが確認できます。あとは,各集合の中からMAX関数で最大の日付(図2中の太字の箇所です)を選択すればOK。こういうある集合の中で,基準値より小さい最大の要素のことを,集合論では最大下界(greatest lower bound)と呼んでいます注3)⁠過去の直近の日付を求めるとは,つまり最大下界を求めることと同義なのです。

S0~S5の部分集合は,図3からも明らかなように,

図3 最大下界を得るためのノイマン型再帰集合

画像

という包含関係のある部分集合群です注4)⁠こういう同心円的な集合は,数学的にはフォン・ノイマンによる再帰集合の構造にそのアイデアの源泉が求められます注5)⁠

現在の日付の処理量と,直近の日付の処理量も併せて表示したいなら,リスト5のようにcur_load_amtとlatest_load_amtの2列を追加すればよいでしょう(実行結果は図4)⁠

リスト5 処理量も併せて表示する(執筆時点ではPostgreSQLでのみ動作)

SELECT LS0.sample_date AS cur_date,
       MAX(LS0.load_amt)   AS cur_load_amt,
       MAX(LS1.sample_date) AS latest,
       (SELECT MAX(load_amt)
          FROM LoadSample
         WHERE sample_date = MAX(LS1.sample_date)) AS latest_load_amt
  FROM LoadSample LS0
           LEFT OUTER JOIN LoadSample LS1
             ON LS1.sample_date < LS0.sample_date
 GROUP BY LS0.sample_date;

図4 リスト5の実行結果(処理量も併せて表示)

cur_date   cur_load_amt  latest      latest_load_amt
---------- ---------     ----------  -----------
2008-02-01     1024             
2008-02-02     2366      2008-02-01         1024
2008-02-05     2366      2008-02-02         2366
2008-02-07      985      2008-02-05         2366
2008-02-08      780      2008-02-07          985
2008-02-12     1000      2008-02-08          780

ただし注意が必要なのは,latest_load_amtを求めるサブクエリ内のWHERE句でMAX関数を使っていることです。LS1.sample_dateは外側のGROUP BY句によって集約されているため,SELECT句では集約関数を適用する形でしか参照することができないのです。したがってこれは標準SQLに則った妥当なSQLなのですが,現在ではPostgreSQLでしか正しく動作しません注6)⁠

そこで代替案として,リスト6のように最大下界を求めるロジックをWHERE句に移す方法が考えられます。

リスト6 最大下界を求めるロジックをWHERE句に記述

SELECT LS0.sample_date AS cur_date,
       LS0.load_amt    AS cur_load,
       LS1.sample_date AS latest,
       LS1.load_amt    AS latest_load
  FROM LoadSample LS0
           LEFT OUTER JOIN LoadSample LS1
             ON LS1.sample_date = (SELECT MAX(sample_date)
                                     FROM LoadSample
                                    WHERE sample_date < LS0.sample_date);
                                    

これも結果は先ほどと同じになります注7)⁠

注3)
反対概念はもちろん「最小上界」⁠least upper bound)です。非等値結合の不等号の向きを逆にして,MIN関数を使うことで取得できます。練習として試してみてください。
注4)
「⊃」は集合の包含関係を表す記号で,たとえば「哺乳類⊃人類」のように使います。今,S0~S5は,大きな数字の集合が小さな数字の集合をすっぽり含む構造になっています(S0は空集合ですが,空集合は定義上すべての集合に含まれるとされるので,やはりS1~S5のすべてに含まれます)⁠
注5)
この再帰集合の理論的背景については,⁠指南書』⁠1-2 自己結合の使い方」および「2-7 SQLと再帰集合」で詳細に取り上げたので,そちらも参照のほど。
注6)
この一見すると構文違反に見える(でも適法な)SQLの論理的な背景については,⁠指南書』「2-10 SQLにおける存在の階層」を参照。
注7)
Oracle 10gでは,外部結合の条件にサブクエリを指定できないという制限があるため,このクエリは動作しません。その場合は内部結合に変える必要がありますが,そうすると今度は現在の日付が「2008-02-01」の行が結果に現れないという不便が生じます。悩ましいものです。ほかの実装にはこういう不都合はありません。

著者プロフィール

ミック

SI企業に勤務するDBエンジニア。主にデータウェアハウス業務に従事している。自身のサイト「リレーショナル・データベースの世界」でデータベースとSQLについての技術情報を公開している。『Web+DB Press』で「SQLアタマアカデミー」を連載中。

著書:『達人に学ぶ SQL徹底指南書』(翔泳社、2008)訳書:J.セルコ『SQLパズル 第2版』(翔泳社、2007)

SQLアタマアカデミー:サポートページ