日別アーカイブ: 2018/08/26

INとEXISTSはどちらが速いのか?

問題:「SALARY > 10000」となる社員が所属している部署を表示せよ

今回は、セミジョイン(セミ結合)について考えてみたい。

セミジョインは通常のジョイントと異なり、2つのクエリー間に親子(主従)関係があるのが特徴である。つまり、メインクエリーがあってそれに従属するサブクエリーから成るのがセミジョインで、ジョインにおいて2つのクエリー(テーブル、ビュー)が(実行順はあるが)対等関係にあるのとは明確に異なる。

Oracleでは、IN述語EXISTS述語を使用するものをセミジョイン(セミ結合)NOT IN述語NOT EXISTS述語を使用するものをアンチジョイン(アンチ結合)と呼んでいる。(リンクは12cR1 SQLチューニングガイドの該当箇所)

使用するテーブル

今回はHRサンプルスキーマのDEPARTMENTS(部署)表とEMPLOYEES(社員)表を使ったクエリーを考えてみる。

SQL> desc DEPARTMENTS
 名前                                    NULL?    型
 ----------------------------------------- -------- ----------------------------
 DEPARTMENT_ID                             NOT NULL NUMBER(4)
 DEPARTMENT_NAME                           NOT NULL VARCHAR2(30)
 MANAGER_ID                                         NUMBER(6)
 LOCATION_ID                                        NUMBER(4)

SQL> desc EMPLOYEES
 名前                                    NULL?    型
 ----------------------------------------- -------- ----------------------------
 EMPLOYEE_ID                               NOT NULL NUMBER(6)
 FIRST_NAME                                         VARCHAR2(20)
 LAST_NAME                                 NOT NULL VARCHAR2(25)
 EMAIL                                     NOT NULL VARCHAR2(25)
 PHONE_NUMBER                                       VARCHAR2(20)
 HIRE_DATE                                 NOT NULL DATE
 JOB_ID                                    NOT NULL VARCHAR2(10)
 SALARY                                             NUMBER(8,2)
 COMMISSION_PCT                                     NUMBER(2,2)
 MANAGER_ID                                         NUMBER(6)
 DEPARTMENT_ID                                      NUMBER(4)

実行環境は

SQL> select BANNER from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
PL/SQL Release 12.2.0.1.0 - Production
CORE	12.2.0.1.0	Production
TNS for Linux: Version 12.2.0.1.0 - Production
NLSRTL Version 12.2.0.1.0 - Production

である。

1. IN述語を使ったSQL

1番目はIN述語を使ったSQLである。

DEPARTMENTS表にアクセスするメインクエリーに対して、EMPLOYEES表にアクセスするサブクエリーをIN述語で連結する。CBOが正しく判断できるようにサブクエリーはカッコで囲む。

このSQLを記述通りに解釈すると、最初にサブクエリーが実行され条件を満たすDEPARTMENT_IDの集合が作られ(このサブクエリーのみの実行では重複が発生することに注意)メインクエリーで使用するINリストが作成される。メインクエリーはこのINリストを使用してDEPARTMENTS表から必要な情報を取得する。

select
   DEPARTMENT_ID
  ,DEPARTMENT_NAME
from
  DEPARTMENTS
where
  DEPARTMENT_ID in (
    select
      DEPARTMENT_ID
    from
      EMPLOYEES
    where
      SALARY > 10000
  )
;

2. EXISTS述語を使ったSQL

次はEXISTS述語を使ったSQLである。

このSQLは(文字通りの解釈では)前項と逆でメインクエリーが先に実行される。次にメインクエリーの結果セットの各行に対してサブクエリー側で条件に合致するかを判定する。

条件に合致すれば(真:TRUE)結果セットに残り、合致しなければ(偽:FALSE)結果セットから除外される。つまり、サブクエリーはメインクエリーのフィルタとして機能する。

メインクエリーとサブクエリーの関係は「D.DEPARTMENT_ID = E.DEPARTMENT_ID」の条件で絞り込む必要がある。万一この条件を書き忘れると、EMPLOYEES表の中にDEPARTMENTS表に存在するDEPARTMENT_IDが1つでも存在すれば、EXISTS述語が常に真(TRUE)となりDEPARTMENTS表の全行が返ることになるので、肝心の「SALARY > 10000」という条件が効かない結果となってしまう。

select
   DEPARTMENT_ID
  ,DEPARTMENT_NAME
from
  DEPARTMENTS D
where
  exists (
    select
      *
    from
      EMPLOYEES E
    where
      D.DEPARTMENT_ID = E.DEPARTMENT_ID
    and SALARY > 10000
  )
;

3. 内部結合を使ったSQL

セミジョインのSQLは次のような内部結合を使ったSQLに書き換えることができる。(書き換えることができるというのは違うSQLでも同じ結果を返す、という意味である。)

注意点としては、DISTINCT句忘れてはいけないことである。(DISTINCTが必要な理由は最後に示す。)

select distinct
   D.DEPARTMENT_ID
  ,D.DEPARTMENT_NAME
from
  DEPARTMENTS D
  inner join EMPLOYEES E
  on  D.DEPARTMENT_ID = E.DEPARTMENT_ID
where
  E.SALARY > 10000
;

どの書き方が優れているのか?

このように同じ結果を得る(であろう)3つのSQLを紹介したのだが、実際はどの書き方がより優れているのだろうか?
違いがあるのであれば、開発者はどんな点に注意した方がよいのだろうか?

性能的な優劣を比較するのであれば、大量データを使用して実行時間を比較するのが王道であるのだが、サンプル表を使用しての簡単な検証になるので、今回は実行計画を確認しながら実行する。

1. IN述語を使ったSQL

		  
SQL> select
  2     DEPARTMENT_ID
  3    ,DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS
  6  where
  7    DEPARTMENT_ID in (
  8      select
  9        DEPARTMENT_ID
 10      from
 11        EMPLOYEES
 12      where
 13        SALARY > 10000
 14    )
 15  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           90 Executive
          100 Finance
           30 Purchasing
           80 Sales
           20 Marketing
          110 Accounting

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2317224448

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |    11 |   253 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |             |    11 |   253 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("DEPARTMENT_ID"="DEPARTMENT_ID")
   3 - filter("SALARY">10000)


統計
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         16  consistent gets
          0  physical reads
          0  redo size
        778  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          6  rows processed

今回の問題に対する解答は、上記6つの部署となることがわかる。

細かい考察は後にしてEXISTS述語を使ったSQLも同様に見てみよう。

2. EXISTS述語を使ったSQL

SQL> select
  2     DEPARTMENT_ID
  3    ,DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6  where
  7    exists (
  8      select
  9        *
 10      from
 11        EMPLOYEES E
 12      where
 13        D.DEPARTMENT_ID = E.DEPARTMENT_ID
 14      and SALARY > 10000
 15    )
 16  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           90 Executive
          100 Finance
           30 Purchasing
           80 Sales
           20 Marketing
          110 Accounting

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2317224448

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |    11 |   253 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |             |    11 |   253 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   3 - filter("SALARY">10000)


統計
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         16  consistent gets
          0  physical reads
          0  redo size
        778  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          6  rows processed

2つの実行計画を比較するには「Plan hash value」を見ればよいのだが、何と同じ「2317224448」となっていることがわかる。

つまり、セミジョインはどちらの書き方をしても同じ実行計画が選択されるという興味深い結果が明らかになった。

この実行計画はHASH JOINなので、メインクエリーのDEPARTMENTS表がビルド表となり、サブクエリーのEMPLOYEES表がプローブ表となっている。

Id=1のオペレーションは「HASH JOIN SEMI」であるが、対応する述語情報(Predicate Information)は

1 - access("DEPARTMENT_ID"="DEPARTMENT_ID")

となっている。
1.の書き方では、このような結合条件を記述していないにもかかわらず、CBOは裏でちゃんとこのような結合条件を使ったセミジョインを考慮してくれている。

従って、素直に解釈するとOracle12cでは「1. IN述語を使ったSQL」でも内部的には「2. EXISTS述語を使ったSQL」にリライトされているように見える。

ただし、マニュアル(SQLチューニングガイド)の記述を見ると、セミジョイン、アンチジョインは内部的には結合(ジョイン)タイプとして処理されると説明されている。(「…セミ結合とアンチ結合は、それらを実行するSQL構文が副問合せであっても、結合タイプとして考慮されます。これらは、副問合せ構文を結合形式で解決できるようにフラット化するため、オプティマイザによって使用される内部アルゴリズムです。…」)

3. 内部結合を使ったSQL

ここまで来ると、残りの内部結合を使ったSQLも気になる。ひょっとして同じ実行計画となるのであろうか?

SQL> select distinct
  2     D.DEPARTMENT_ID
  3    ,D.DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6    inner join EMPLOYEES E
  7    on  D.DEPARTMENT_ID = E.DEPARTMENT_ID
  8  where
  9    E.SALARY > 10000
 10  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
          100 Finance
           90 Executive
           30 Purchasing
          110 Accounting
           80 Sales
           20 Marketing

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 1983137394

-----------------------------------------------------------------------------------
| Id  | Operation           | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |    11 |   253 |     7  (15)| 00:00:01 |
|   1 |  HASH UNIQUE        |             |    11 |   253 |     7  (15)| 00:00:01 |
|*  2 |   HASH JOIN SEMI    |             |    11 |   253 |     6   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  4 |    TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   4 - filter("E"."SALARY">10000)


統計
----------------------------------------------------------
        302  recursive calls
          4  db block gets
        324  consistent gets
          0  physical reads
          0  redo size
        778  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         16  sorts (memory)
          0  sorts (disk)
          6  rows processed

今度は違う実行計画となった。(Plan hash value: 1983137394)

このクエリーはセミジョインでないにもかかわらず、Id=2で「HASH JOIN SEMI」となっているのが興味深い。

さらに、この実行計画ではId=1の「HASH UNIQUE」が実行されている。
つまり、セミジョインに比べてDISTINCT付きジョインは、オペレーションが1つ多くなっている分性能的に不利なのではないかと思われる。(あくまでも実行計画を比較した上での見解)

DISTINCT句を外してみると

参考までに、上のクエリーでDISTINCT句を外して実行してみる。
(重複排除すべき行を網掛け表示にしてある。)

SQL> select
  2     D.DEPARTMENT_ID
  3    ,D.DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6    inner join EMPLOYEES E
  7    on  D.DEPARTMENT_ID = E.DEPARTMENT_ID
  8  where
  9    E.SALARY > 10000
 10  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           90 Executive
           90 Executive
           90 Executive
          100 Finance
           30 Purchasing
           80 Sales
           80 Sales
           80 Sales
           80 Sales
           80 Sales
           80 Sales
           80 Sales
           80 Sales
           20 Marketing
          110 Accounting

15行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2052257371

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |    68 |  1564 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN         |             |    68 |  1564 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   3 - filter("E"."SALARY">10000)


統計
----------------------------------------------------------
          4  recursive calls
          4  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
        860  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         15  rows processed

DISTINCT句を付けないと「HASH JOIN」となることがわかる。

セミジョインの結合方式を考える

上記セミジョインの結合方式は、「HASH JOIN SEMI」というオペレーション名からハッシュ型セミジョインであることがわかるが、以下のようにサブクエリー側にヒント句を使用することで結合方式を変更することができる。

ネステッドループ型セミジョイン

SQL> select
  2     DEPARTMENT_ID
  3    ,DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6  where
  7    exists(
  8      select
  9        /*+ NL_SJ */
 10        *
 11      from
 12        EMPLOYEES E
 13      where
 14        D.DEPARTMENT_ID = E.DEPARTMENT_ID
 15      and SALARY > 10000
 16    )
 17  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           20 Marketing
           30 Purchasing
           80 Sales
           90 Executive
          100 Finance
          110 Accounting

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2332702268

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |    11 |   253 |    41   (0)| 00:00:01 |
|   1 |  NESTED LOOPS SEMI |             |    11 |   253 |    41   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMPLOYEES   |    28 |   196 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID" AND "SALARY">10000)


統計
----------------------------------------------------------
          0  recursive calls
         56  db block gets
        220  consistent gets
          0  physical reads
          0  redo size
        778  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          6  rows processed

以前のバージョンでは、セミジョインのデフォルトの結合方式であったが、この検証においては3つの中でも最もコストが高くなった。

細かいが、結果がDEPARTMENT_IDの昇順となっている点がハッシュ型と異なっていることがわかる。

ソートマージ型セミジョイン

SQL> select
  2     DEPARTMENT_ID
  3    ,DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6  where
  7    exists(
  8      select
  9        /*+ MERGE_SJ */
 10        *
 11      from
 12        EMPLOYEES E
 13      where
 14        D.DEPARTMENT_ID = E.DEPARTMENT_ID
 15      and SALARY > 10000
 16    )
 17  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           20 Marketing
           30 Purchasing
           80 Sales
           90 Executive
          100 Finance
          110 Accounting

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2249117780

-----------------------------------------------------------------------------------
| Id  | Operation           | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |    11 |   253 |     8  (25)| 00:00:01 |
|   1 |  MERGE JOIN SEMI    |             |    11 |   253 |     8  (25)| 00:00:01 |
|   2 |   SORT JOIN         |             |    27 |   432 |     4  (25)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  4 |   SORT UNIQUE       |             |    68 |   476 |     4  (25)| 00:00:01 |
|*  5 |    TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
       filter("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   5 - filter("SALARY">10000)


統計
----------------------------------------------------------
          0  recursive calls
          4  db block gets
         15  consistent gets
          0  physical reads
          0  redo size
        778  bytes sent via SQL*Net to client
        608  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          6  rows processed

ソートマージ型は、オペレーション数が一番多いにもかかわらず、ネステッドループ型よりもコストが低い。

結果はネステッドループ型と同様にDEPARTMENT_IDの昇順になっている。

セミジョインはサブクエリーによるメインクエリーの存在チェック(フィルタリング)とも言えるが、単純な有無を判定するのであれば並び替えをせずにハッシュテーブル上で比較ができるハッシュ型が有利なのではないかと考える。

筆者の経験では、DBリンク越しのセミジョイン(サブクエリー側のテーブルがリモート表)のあるSQLがネステッドループ型セミジョインとなっていたので、HASH_SJヒントでハッシュ型セミジョインにしたところ、2時間経っても終わらないクエリーがわずか2分強で終了するまでに改善したことがある。

ちなみにHASH_SJヒントは以下のように使用する。

SQL> select
  2     DEPARTMENT_ID
  3    ,DEPARTMENT_NAME
  4  from
  5    DEPARTMENTS D
  6  where
  7    exists(
  8      select
  9        /*+ HASH_SJ */
 10        *
 11      from
 12        EMPLOYEES E
 13      where
 14        D.DEPARTMENT_ID = E.DEPARTMENT_ID
 15      and SALARY > 10000
 16    )
 17  ;

DEPARTMENT_ID DEPARTMENT_NAME
------------- --------------------
           90 Executive
          100 Finance
           30 Purchasing
           80 Sales
           20 Marketing
          110 Accounting

6行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 2317224448

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |    11 |   253 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |             |    11 |   253 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DEPARTMENTS |    27 |   432 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| EMPLOYEES   |    68 |   476 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("D"."DEPARTMENT_ID"="E"."DEPARTMENT_ID")
   3 - filter("SALARY">10000)

おまけ:内部結合になぜDISTINCT句が必要なのか?

今回のようなSQLで内部結合を使う場合DISTINCT句が必要なことは先に述べたが、その理由を考えてみたい。

結合(ジョイン)は2つのテーブルから結果を取得するものであるが、「部署を表示せよ」ということであれば片方のテーブルに属するカラムのみを表示させることになる。

問題の主旨からすれば本来は「DEPARTMENT_NAME」のみを取得するだけでよかったのだが、今回はわかりやすくするために「DEPARTMENT_NAME」が従属する主キーである「DEPARTMENT_ID」も表示するようにした。

同様に、「SALARY > 10000」という条件で絞り込んだEMPLOYEES表を結合することを考えると、各行の主キーである「EMPLOYEE_ID」も一緒に結合すると考えることができる。

これを実際のクエリーで示したのが以下となる。

SQL> select
  2     D.DEPARTMENT_ID
  3    ,D.DEPARTMENT_NAME
  4    ,E.EMPLOYEE_ID "(EMPLOYEE_ID)"
  5  from
  6    DEPARTMENTS D
  7    inner join EMPLOYEES E
  8    on  D.DEPARTMENT_ID = E.DEPARTMENT_ID
  9  where
 10    E.SALARY > 10000
 11  ;

DEPARTMENT_ID DEPARTMENT_NAME (EMPLOYEE_ID)
------------- --------------- -------------
           20 Marketing                 201
           30 Purchasing                114
           80 Sales                     145
           80 Sales                     146
           80 Sales                     147
           80 Sales                     148
           80 Sales                     149
           80 Sales                     162
           80 Sales                     168
           80 Sales                     174
           90 Executive                 100
           90 Executive                 101
           90 Executive                 102
          100 Finance                   108
          110 Accounting                205

15行が選択されました。

従って、このクエリーからEMPLOYEE_IDを非表示(SELECTリストから外す)としても、DISTINCT句を付けない限り結果は重複表示される。

これが、DISTINCT句付き内部結合としなければならない理由である。

まとめ

今回のタイトルは「INとEXISTSはどちらが速いのか?」にしてみたが、開発の現場ではこのような疑問が生じることが多々あるかと思う。

どちらでも結果が変わらない記述法があると「果たしてどちらがいいのか?」という議論になり、あまり望ましくない結論として「どちらかに統一してしまえ」ということになったりする。

「実行計画が変わらないのであればどちらでもよいではないか?」それはそれで問題ないのであるが、次のようなやっかいなトラブルが起きる可能性がある。

昔のバージョンにおいてEXISTS述語にすることで大きく性能改善させた成功体験のあるベテランSEがいたとする。

新人SEが一生懸命IN述語で書いてきたSQL文を見て、ベテランSEが「INなんかダメだ!EXISTSに書き直せ!」などと安直に指示したりすると、新人SEは一括置換でEXISTSに直したりするかもしれない。

本文でも紹介したように、EXISTSの場合はサブクエリーに結合条件を記述しなければならないのに、それがスッポリ抜け落ちてしまう可能性があり、しかも構文エラーにならないので間違いに気づかない危険性もある。

どちらかに統一するような不毛な議論をするのではなく、どちらでもよいのだという柔軟さを持つべきなのではないだろうか。

CBOは日々進化しており、以前の常識が通用しないかもしれないということをベテランは自覚し、最新バージョンではどうなっているのかという疑問を常に持ち続ける必要があるのではないかと、自戒を込めて主張したい。