Contents
EXISTS述語
通常のサブクエリであれば、「内側の副問い合わせを1回処理⇒主問い合わせ(外側クエリ)を1回処理」というシンプルな手順だが、相関サブクエリ(副問い合わせ)を使用したSQL文は「外側クエリでテーブルから行を絞り込む過程で、各行について抽出してもよいかを判定するために、繰り返し副問い合わせを実行する」ので、DBMSの負荷がかかり、敬遠されがち。
ただ、この相関サブクエリを利用してもパフォーマンスが良く、複数行を一単位とみなした高度な条件記述ができるのがEXISTS述語。EXISTS述語は「量化」という述語論理の機能を実現することができる。
EXISTS述語の「述語」とは、戻り値が真偽値になる関数を指している。=、<、>、BETWEEN、IN、IS NULL等々も、返り値が真偽値なので述語として捉えることができる。
特徴
EXISTS述語の特徴は入力値が「行の集合」である点にある。これは他の述語と比較すると分かりやすい。
1 2 3 4 5 6 7 8 9 10 |
SELECT * FROM Bar B WHERE F.id = B.id ; -- 「=」への入力値は一行(F.id) SELECT id FROM Foo F WHERE EXISTS (SELECT * FROM Bar B WHERE F.id = B.id ); -- 「EXISTS」への入力値はSELECTで指定した「行の集合」 |
「行の集合」を入力するからこそ、複数行を一単位とみなした高度な条件記述ができる。
述語論理には、量子化と呼ばれる述語があり、
①「全称量化子」⇒ 全てのxは条件Pを満たす (for All x, 〜)
②「存在量子化」⇒ 条件Pを満たすxが(少なくとも1つ)存在する (there Exists x that 〜)
という2つを意味を含有している。
EXISTS述語は②の「存在量子化」を実装した述語。「じゃ、全称量化子は・・・」となるが、残念ながらSQLには①の「全称量化子」を実装した述語が存在しない。じゃ、全称量化子(全てのxは条件Pを満たす)を実装したかったらどうするのか?
答えは②の「存在量子化(=EXISTS述語)」を使った二重否定で表現すること。
つまり、
全ての行について条件Pを満たす = 条件Pを満たさない(否定1回目)行が1つも存在しない(否定2回目)
と命題を言い換える必要があるということ。
ケーススタディ(学校の生徒とテストの点数)

全ての教科が50点以上の生徒はだれ?
全ての教科が50点以上 = 50点以上でない(50点未満)subjectが1つも存在しない
1 2 3 4 5 6 7 |
SELECT DISTINCT student_id FROM Scores S1 WHERE NOT EXISTS -- NOT EXISTSを使って「行の集合」に対して否定をかける(否定2回目) (SELECT * FROM Scores S2 WHERE S1.student_id = S2.student_id -- 相関サブクエリを使用 AND S1.score < 50); -- 50点以上でない(否定1回目)のsubject |
数学の点数が80点以上、国語の点数が50点以上の生徒はだれ?
数学の点数が80点以上、国語の点数が50点以上
= 数学の点数が80点未満で、国語の点数が50点未満のsubjectが存在しない
1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT student_id -- GROUP BYで集約しているので、DISTINCTによる重複削除の必要はない FROM Scores S1 WHERE subject IN('数学','国語') -- 数学と国語のみが対象なので、先に絞り込んでおく AND NOT EXISTS (SELECT * FROM Scores S2 WHERE S1.student_id = S2.student_id AND 1 = CASE WHEN (subject = '数学' AND score < 80) THEN 1 CASE WHEN (subject = '国語' AND score < 50) THEN 1 ELSE 0 END); GROUP BY student_id HAVING COUNT(*) = 2; -- これで数学と国語の2教科が揃っているという条件を付加 |