今月から仕事内容が変わり、本格的にSQLを使うことになりました。
SQLは情報処理試験で勉強したものの、業務でしっかり使うのは初めての経験。
ので、しばらくは、知らなかったSQLを1つずつまとめていきたいと思います。
今日はIN句とEXISTS句について。
※Oracle Database19cで確認しています。
データベースによって異なる部分などあるかもしれないため、自己責任でお読みください。
もくじ
1.IN句 (役割と基本の使用方法/応用編)
2.EXISTS句(役割と基本の使用方法/応用編)
4.IN句とEXISTS句の否定形( NOT IN/NOT EXISTS/NOT INの注意点 )
1.IN句
■役割と基本の使用方法
「OR(または)」の条件を、よりコンパクトにわかりやすく記述できるようにするための句。
例えば、列Aの値が「1」または「2」である場合のみ、レコードを取り出す、
といった処理はよくあると思う。
この処理を実現するためのコードとして、以下のような書き方が可能ではある。
SELECT *
FROM TEST_TABLE
WHERE COLUMN_A = 1 OR COLUMN_A = 2;
が、「COLUMN_A」という列名を、「COLUMN_B」という列名に変えたいとき、
わざわざ2か所も変えないといけない手間が生じる。
また、「1」か「2」だけでなく「3」であるときもレコードを取り出したいとなると、
「OR COLUMN_A = 3」という同じようなコードをさらに追加する必要があり、どうしても冗長となってしまう。
一方、IN句を利用すると、同じ意味のコードを以下のようにまとめて書くことが可能である。
SELECT *
FROM TEST_TABLE
WHERE COLUMN_A IN (1, 2);
WHERE句内に、列名 IN (OR条件とする値(カンマ区切り)) を書くこととなるため、
列名を何度も書く必要がなくなり、コードの可読性も上がる。
もちろん、「3」の条件を加えたいときも、「IN (1, 2, 3)」のようにすればOKである。
■応用編
IN句で指定できるのは、カラムに格納された値そのもの(上の例でいえば、「1」や「2」という値)だけではない。
以下のようなSQLの入れ子での利用も可能である。
SELECT *
FROM TEST_TABLE
WHERE COLUMN_A IN (
SELECT ID
FROM ID_TABLE
);
この場合、まずはIN句内(内側)のSELECT文が実行され、IDテーブルの各ID値が取得される。
取得された各ID値がIN句に代入され、外側のSELECT文が実行されることとなる。
私はまだ実務で入れ子式のコードにはお目にかかっていないが、
ググるとけっこう出てくるので、よくある使い方なんだと思う。
2.EXISTS句
■役割と基本の使用方法
EXISTS句は、「SQLの実行結果が存在するかどうか」を条件としたいときに利用する。
存在する場合(TRUEの場合)のみ、SQLを実行することになる。
例えば、以下のように記述する。
SELECT *
FROM TEST_TABLE
WHERE EXISTS(
SELECT *
FROM ID_TABLE
WHERE ID = 1
);
EXISTS句でもIN句と同様、まずはEXISTS句内(内側)のSELECT文が実行される。
上記例では、IDが1であるレコードを、IDテーブルから取得するというSQLが先に実行される。
EXISTS区内のSQLで、結果が1つ以上返された場合(=EXISTつまり存在がTRUEの場合)は、
外側のSQLである、「SELECT * FROM COFFEE」部分が実行される。
結果が1つもなかった場合(=EXISTつまり存在がFALSEの場合)は、外側のSQLは実行されない。
■応用編
EXISTSは、外部結合を併用することができる。
例えば…
SELECT *
FROM TEST_TABLE
WHERE EXISTS(
SELECT *
FROM ID_TABLE
WHERE TEST_TABLE.COLUMN_A = ID_TABLE.ID
);
通常、外部結合するには、JOIN句などで結合を記述する必要があるが、
EXISTS句内ではJOIN句を記述せず、上の例のようにすれば外部結合してくれる。
ちなみに外部結合が出てきたとしても、
「EXISTS句内を先に確認し、存在有無で外側のSQLを実行するかどうか決める」流れは基本通りである。
3.IN句とEXISTS句の違い
IN句はCOLUMN_A列の値が「1か2」であるレコードを抽出し、当該レコードに対して処理をする。
EXISTS句はCOLUMN_A列の値が「1か2であるレコードが存在する場合」、処理を実行する。(EXISTS句によるレコードの抽出はない)
4.IN句とEXISTS句の否定形
IN句、EXISTS句とも、それぞれ否定形が存在する。
構文自体はどちらもいたってシンプルで、それぞれ「NOT IN~」、「NOT EXISIS~」と、頭に「NOT」をつけてやればOKである。
■NOT IN
IN句内に記述した各値のいずれでもないレコードに対し、外側のSQLを実行することになる。
例えば以下のようなSQLであれば、COLUMN_Aの値が1でも2でもないレコード(3とか)に対して処理を実行する。
SELECT *
FROM TEST_TABLE
WHERE COLUMN_A NOT IN( 1, 2 );
■NOT EXISTS
EXISTS句内のSQLを実行した結果、該当するレコードがなかった場合のみ、外側のSQLを実行することになる。
例えば次のSQLなら、IDテーブルにID列の値が1であるレコードがなかった時に、
TESTテーブルの各レコードを取得する処理となる。
ID列の値が1のレコードがあった場合は、何もしない。
SELECT *
FROM TEST_TABLE
WHERE NOT EXISTS(
SELECT *
FROM ID_TABLE
WHERE ID = 1
);
■NOT INの注意点
NOT INで気をつけねばならないことがある。
それは、IN句の選択肢の中にNULLがある場合、想定した処理結果にならないということである。
例えば以下のSQLがあり、ID_TABLEには、ID=5となるレコードがない(NULL)とする。
SELECT *
FROM TEST_TABLE
WHERE COLUMN_A NOT IN (
SELECT ID
FROM ID_TABLE
WHERE ID = 4 OR ID = 5
);
1つでもNULLになるものをIN句内で含むと、他にいくつ条件を書いていても、IN句は何も結果を返してくれない(結果的に、IN句が無かったかのようになる)。
従って、今回であれば4と5をIN句内に含んでいるため、
本来ならCOLUMN_Aが4でも5でもないレコードをSELECTしてくるはずが、
COLUMN_Aが4や5のレコードもSELECTされてしまう。
意図しない処理結果になることを防ぐには、
IN句内のWHERE条件に、「IS NOT NULL」(NULLでない場合にレコードを取得する)を追加する必要がある。
ちなみにNOT INは、処理が重くなるという情報がちらほらとネットにあった。
実際どれくらい重くなるのか(ないし、気にするほどでもないのか)は環境にも依存すると思われるが、
NOT INは上記のようなNULL問題もあり、積極的に使うようなものではないのかもしれない。
今回は以上!