SQLer 生島勘富 のブログ

RDB・SQLの話題を中心に情報発信をしています。

INとEXISTSの違い

 INとEXISTSは違います。

 BETWEENと、不等号の組合わせなど、等価になる記述法はあるのですけれど、INとEXISTSは基本的に同じ結果を返すことが可能ですが、意味は違います。

 この違いが分かるにはインデックスを理解する必要がありますので、まずは、インデックスのイメージをつけてください。

まずはイメージ

 ここでも、まずはイメージで考えましょうね。

 あなたは先輩の結婚式の司会を頼まれましたとします。イロイロと準備がありますが、余興で歌を歌う人がいるとき、予めカラオケの番号を調べておくでしょう。

 食事の間のBGMについては、ラブソングの入ったiPodをつないでランダムで流すことにしましょう。しかし、新郎新婦にとって(過去の恋愛経験上)都合の悪い曲があり、チェックしてはじいておくことにしました(なかなか、そつがない司会ですな)。

 これらの処理をSQLにするならば……。

■カラオケの番号を準備するのがINの処理。
     SELECT *
     FROM カラオケ ……
     WHERE
       カラオケ番号 IN
         (SELECT カラオケ番号
         FROM 歌うリスト
         )

■BGMをiPodから流す曲を準備するのがEXISTSの処理になります。
     SELECT *
     FROM iPod
     WHERE
       NOT EXISTS
         (SELECT *
         FROM 新郎新婦NGリスト
         WHERE 新郎新婦NGリスト.曲名 = iPod.曲名
         )
 ※ 赤字の部分にインデックスがあればオプティマイザはインデックスを使います。

何を基準に決めればよいか

 人間が面倒なことは、コンピュータがやってもやっぱり面倒(コストが掛かる)のです。文法じゃなく、どちらかに決め打ちでもなく、自分が手作業でするとしたらどちらを選ぶか考えれば自ずと答えは出てきます。

 INを使うとき、つまりカラオケの番号を準備するときは、歌うリストをメインループに選んでいることが分かるでしょうか?逆に、EXISTSを使うときは、iPodをメインループに選んでいます。

 つまり、

■INの場合
     foreach (int 番号 in 歌うリスト)
     {
       結果セット.add(カラオケ.find(番号));
     }

â– EXISTS
     foreach (string 曲名 in iPod)
     {
       if(Arrays.binarySearch(新郎新婦NGリスト, 曲名) < 0)
       {
         結果セット.add(曲名);
       }
     }

 と考えても良い。

 メインループと結果セットに入れるのが同じとき(読み飛ばし)のときはEXISTS、メインループと結果セットに入れるものが違うときはINになります。

 処理から考えると、「SQLは仕様書である」という意味が分かると思います。

 手続き型言語でメインループを逆にしても同じ答えが返ってくるのと同じで、INでもEXISTSでも等価の答えを返すことができます。しかし、新人ならともかく、ベテランがそんな仕様書・プログラムを書くのは相当に恥ずかしいことでしょう。

 ところが、SQLではコーディング規約から、「INで書いて遅いときには、EXISTSにしてみる」とか、プロとしてかなり恥ずかしい内容が書いてあることは珍しくないです。意味が分かると、あってはならないことだと分かると思いますが……。

もう一つの選び方

 処理から考えても良いのですけれど、もう一つの選び方があります。使いたいインデックスがある場合は、
 
■INのときは
     カラオケ.カラオケ番号

■EXISTSのときは、
     新郎新婦NGリスト.曲名

 のインデックスが使用可能ですので、それを手がかりに決定してもかまいません。

 手続き型の処理を見れば分かりますが、foreachは要素がなくなるまで(breakするまで)順にアクセスしますので、メインループの方はインテックスがあろうが、なかろうが、関係はないです(もちろん、他のWHERE条件があるときはメインループのインデックスを使うことはあり得ます)。

 とにかく、INとEXISTSは等価の結果を返すことが可能ですが意味は違います。処理から考えても良いし、どちらのインデックスを使いたいかで考えても良いけれど、意味を考えないで決め打ちはやめましょう。意味を考えないで書かれた長いSQLは、何がしたいのか分からないし、全く読めないものになっています。SQLは魔法でも呪文でもないので、必ず、意味を考えて書いてください。

余談です

 私の知る限り、SQLServer(Accessも)以外のRDBMSでは、

     WHERE
       (カラム1, カラム2) IN
         (SELECT カラムA, カラムB
         FROM サブテーブル
         WHERE 条件
         )

 という「行値構成子」とか「行値式」とかいうSQL99からの書き方が可能ですが、SQLServerではこの書き方ができません。そのため、複数のカラムが条件として必要なときには、SQLServerではEXISTSで書くしかありません。

 SQLServerのこの仕様は、どうにもならないかなと……。なんとかして欲しいところですね。