MySQLで、Oracleの分析関数と同じ結果を取得する3
山岸賢治 [著] 2008/11/26 14:00
このエントリーをはてなブックマークに追加

SourceCode 3.03 KB
1 2 →

 連載「分析関数の衝撃」では、Oracleの分析関数を主に扱いました。「分析関数の衝撃」の外伝となるこの連載では、 MySQLで、Oracleの分析関数と同じ結果を取得するSQLの考え方と、処理のイメージを解説します。今回は、Lag関数とLead関数と同じ結果を取得するSQLを扱います。

はじめに

 本稿では、Oracleの分析関数の中で、Lag関数とLead関数と同じ結果を取得するSQLを扱います。

対象読者

  • MySQLやPostgreSQLで、分析関数と同じ結果を取得するSQLを書きたい方
  • OracleやDB2やSQLServerの、分析関数の理解を深めたい方

 本稿では、相関サブクエリを多用しますので、『相関サブクエリで行と行を比較する』を先に読んでおくと理解が進むと思います。

必要な環境

 本稿で扱うSQLは、MySQL 5.1.28で動作確認しました。ソースコードはPostgreSQL 8.3とOracle 10.2.0.1.0でも動作確認しました。その他、次の環境でも応用が可能です。

  • SQLServer
  • DB2

1. IDごとのSeqの昇順での1行前と1行後の行のValを求める

 最初は、IDごとのSeqの昇順で、1行前と1行後の行のValを求めるSQLについてです。まずは、テーブルのデータと出力結果を考えます。

IDTable
IDSeqVal
AA1100
AA2100
AA3500
AA4200
AA5200
AA650
BB1200
BB2400
BB3800
BB4900
CC1100
CC2800
CC3700
DD1400
EE150
FF110
FF320
FF540
FF680

 IDごとのSeqの昇順での1行前と1行後の行のValを求めます。1行前の行がなければ、その値はnullとします。同様に、1行後の行がなければ、その値はnullとします。言いかえれば、Oracleの下記の分析関数を使ったSQLと同じ結果を取得します。

分析関数を使ったSQL
select ID,Seq,Val,
Lag(Val)  over(partition by ID order by Seq) as LagVal,
Lead(Val) over(partition by ID order by Seq) as LeadVal
  from IDTable
order by ID,Seq;
出力結果
IDSeqValLagValLeadVal
AA1100null100
AA2100100500
AA3500100200
AA4200500200
AA520020050
AA650200null
BB1200null400
BB2400200800
BB3800400900
BB4900800null
CC1100null800
CC2800100700
CC3700800null
DD1400nullnull
EE150nullnull
FF110null20
FF3201040
FF5402080
FF68040null

 Lag(Val) over(partition by ID order by Seq)について考えてみると、Seqの昇順でソートした時の1行前というのは、ソートキーであるSeqの値が対象行より小さい中で最大のSeqの行(最大下界)だと考えることができます。

 Lead(Val) over(partition by ID order by Seq)について考えてみると、Seqの昇順でソートした時の1行後というのは、ソートキーであるSeqの値が対象行より大きい中で最小のSeqの行(最小上界)だと考えることができます。

 以上をふまえて、答えは下記となります。

Limit句を使うSQL
select ID,Seq,Val,
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and b.Seq < a.Seq
 order by b.Seq desc Limit 1) as LagVal
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and b.Seq > a.Seq
 order by b.Seq Limit 1) as LeadVal
  from IDTable a
order by ID,Seq;

 LagValを求める相関サブクエリでは、where句で自分よりSeqが小さいことを条件として、order by句でSeqの降順にソートして、Limit句で行数を1行のみと指定してます。

 LeadValを求める相関サブクエリでは、where句で自分よりSeqが大きいことを条件として、order by句でSeqの昇順にソートして、Limit句で行数を1行のみと指定してます。

 SQLのイメージは下記です。

SQLのイメージ
SQLのイメージ

 ちなみに、Limit句を使わないSQLは、下記となります。

Limit句を使わないSQL 1(exists述語を使用)
select ID,Seq,Val,
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and exists(select 1 from IDTable c
                where c.ID=a.ID
                  and c.Seq between b.Seq and a.Seq
               having count(*) = 1+1)) as LagVal,
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and exists(select 1 from IDTable c
                where c.ID=a.ID
                  and c.Seq between a.Seq and b.Seq
               having count(*) = 1+1)) as LeadVal
  from IDTable a
order by ID,Seq;

 from句でのネストされたインラインビューなどはネストの深い方から読みますが、上記のようなselect句でのネストされた相関サブクエリはネストの浅い方から読むと分かりやすいです。

 select句での相関サブクエリの中で、exists述語を使用し、where句で同じIDを指定し、between述語でSeqの大小条件を指定して、having句でその行の数を指定しています。

 LagValでは、条件式

Valを取得する行のソートキー <= ソートキー <= 自分の行のソートキー

 を満たす行数が2であることをhaving句で指定しています。

 LeadValでは、条件式

自分の行のソートキー <= ソートキー <= Valを取得する行のソートキー

 を満たす行数が2であることをhaving句で指定しています。

 上記のSQLは、having句での条件指定が単純な等価条件で、かつ、group by句がないので、下記に書き換え可能です。

Limit句を使わないSQL 2
select ID,Seq,Val,
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and (select count(*)
           from IDTable c
          where c.ID=a.ID
            and c.Seq between b.Seq and a.Seq) = 1+1) as LagVal,
(select b.Val
   from IDTable b
  where b.ID=a.ID
    and (select count(*)
           from IDTable c
          where c.ID=a.ID
            and c.Seq between a.Seq and b.Seq) = 1+1) as LeadVal
  from IDTable a
order by ID,Seq;

1 2
→
INDEX
MySQLで分析関数を模倣3(後編)
Page1
はじめに
対象読者
必要な環境
1. IDごとのSeqの昇順での1行前と1行後の行のValを求める
2. IDごとのSeqの昇順での3行前と3行後の行のValを求める
最後に
参考資料
こちらの関連記事もおすすめです

プロフィール
山岸賢治 ヤマギシケンジ

Oracle ACEの1人。
OracleSQLパズルの運営者。
ORACLE MASTER Silver Oracle Database 10g
(研修受講で)ORACLE MASTER Gold Oracle Database 10g
ソフトウェア開発技術者 (情報処理技術者試験)
第二種情報処理技術者 (情報処理技術者試験)
 


記事へのコメント・トラックバック機能は2011年6月に廃止させていただきました。記事に対する反響はTwitterやFacebook、ソーシャルブックマークサービスのコメントなどでぜひお寄せください。

スポンサーサイト