サブクエリを活用できるようになることで、SQLの活用の幅が広がり、複雑なクエリを発行する必要がある場合でも1つのSQL文だけで済むようになります。ここでは主にサブクエリの概要と書き方について解説します。
サブクエリ(副問合せ)とは?
サブクエリとはSQLの中に書くSQLのことです。
SQL文を実行することをクエリ(問合せ)の発行と呼びます。当然の話ですが、クエリを発行するとその結果が表形式で取得できるのがOracleです。
サブクエリが入っているクエリはまずサブクエリから実行され、実行結果を一つのテーブルと見なしながらメインクエリが実行されます。
ビューはビューのSQLから事前に仮想テーブルを作成し他のSQL内で使用するので、サブクエリと近い機能を持っています。
サブクエリの書き方
サブクエリは主にFROM句、WHERE句、SELECT句で記述する場合が多いです。
一つずつ例を見ていきましょう。今回はこちらのテーブルを使います。
EMPLOYEES
DEPARTMENT
BRANCH
FROM句で使う場合
FROM句でサブクエリを使う場合はビューを使う感覚とほぼ同じで、先にビューで仮想テーブルを作っておくか、サブクエリでそのSQLでだけ使う仮想テーブルをサブクエリで作るかの違いだけです。ですので、FROM句で使うサブクエリのことをインラインビューと呼ぶこともあります。実際にFROM句でサブクエリを使ってみましょう。
コード:
SELECT T.*
FROM (
SELECT E.ID, E.NAME, B.NAME AS BRANCH_NAME, D.NAME AS DEPARTMENT_NAME
FROM EMPLOYEES E
JOIN BRANCH B ON E.BRANCH_ID = B.ID
JOIN DEPARTMENT D ON E.DEPARTMENT_ID = D.ID
) T
WHERE T.ID = 1
結果:
FROM句の中で()で囲っている部分がサブクエリです。3つのテーブルを結合したクエリ結果をサブクエリで取得し、さらにその中からIDが1のレコードを取得しています。
FROM句のサブクエリには別名を付けられます。上のサンプルではサブクエリの結果に対してTという別名を付与しました。
付与した別名はSELECT句やWHERE句で通常のテーブルに付与した場合と同じように扱えているのが見て取れます。
WHERE句で使う場合
WHERE句で使う場合は主に右側の条件をサブクエリにする場合が多いです。
例を見てみましょう。
コード:
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID = (SELECT ID FROM DEPARTMENT WHERE NAME = '開発部')
結果:
このようにWHERE句でもサブクエリを使えます。FROM句の時と同じように、サブクエリは()で囲った中に記述します。
EMPLOYEESテーブルは部署名を持っていないので、サブクエリを使って部署名が開発部の従業員情報を取得しています。
FROM句と同じようにサブクエリ内でSQLを記述していますが、上記の例のように条件がイコールで結ばれている場合、サブクエリの結果が複数個あるとエラーになります。
コード:
SELECT *
FROM EMPLOYEES
WHERE
DEPARTMENT_ID = (SELECT ID FROM DEPARTMENT) -- 抽出してないので複数件取得される
結果:
サブクエリが複数の行を返しているというエラーメッセージが表示されました。
このようにサブクエリをWHERE句で使う場合はサブクエリ結果が条件文とマッチするかを考える必要があります。
SELECT句で使う場合
最後にSELECT句で使う場合です。SELECT句ではイコール演算子で結ばれたWHERE句のサブクエリと同じく、単一の結果が返却されるSQLであることが必要です。
コード:
SELECT E.ID,E.NAME,
(SELECT NAME FROM DEPARTMENT WHERE ID = E.ID) DEP_NAME
FROM EMPLOYEES E
結果:
SELECT句内のサブクエリはFROM句やWHERE句と異なりレコード1行に対して1回サブクエリを発行します。ですのでSELECT句のサブクエリが複雑だと他の場所で使用するよりも高負荷で結果を取得するのに時間のかかるクエリになるので、SELECT句で使う場合は比較的単純なサブクエリであることが望ましいです。
上記の例はDEPARTMENTテーブルから各従業員が所属する部署名を取得しています。SELECT句のサブクエリは1行ずつ評価されるので、1行目は佐藤さんのDEPARTMENT_IDである「0」がE.IDに入り、「企画部」を取得します。その後に2行目が評価されます。
1行目と同じく2行目は田中さんのDEPARTMENT_IDである「1」がE.IDに入るので、今度は企画部ではなく「営業部」が取得できているのが結果からも分かります。
このように、SELECT句でのサブクエリは1行1行順番に処理されることをうまく使えば、テーブル結合をせずに同じような結果を取得することも可能です。このようなサブクエリ内でメインクエリの列を参照することを「相関サブクエリ」と呼びます。
サブクエリの結果の判定
サブクエリの前に「=」「IN」「EXIST」を付けた場合のそれぞれの動作の違いについて見ていきましょう。
まずは「=」の場合ですが、=の場合はサブクエリの結果が単数でなければならないのは先ほど説明した通りです。では複数の返却結果を条件としたい場合はどうしたら良いのかというと、その場合は「IN」を使います。
コード:
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID IN (SELECT ID FROM DEPARTMENT WHERE ID <> 1)
結果:
このようにINを使うことで複数の返却結果に対応できます。
コード:
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID IN (SELECT ID, NAME FROM DEPARTMENT WHERE ID <> 1)
結果:
最後に「EXISTS」について見ていきましょう。EXISTSは「EXISTSの後に記述するサブクエリの結果行が存在すればTrue、存在しない場合はFalseを返却する」関数です。
Trueの場合は行を取得でき、Falseの場合は取得できないと見なしても良いです。
EXISTSは基本的に相関サブクエリを使用します。実際に使い方を見てみましょう。
コード:
SELECT *
FROM EMPLOYEES E
WHERE EXISTS (
SELECT *
FROM DEPARTMENT D
WHERE E.DEPARTMENT_ID = D.ID
AND D.ID <> 1
)
結果:
EXISTSは相関サブクエリを使用するので、メインクエリの列の値分1つ1つ評価されていきます。サブクエリ内でDEPARTMENTテーブルのIDが1以外であることが条件となっており、EXISTSは行が存在しない場合Falseを返すので部署IDが1の行のみ結果に出力されませんでした。
これがEXISTSの使い方ですが、実はこれはIN句でも同じことが可能です。なのでINが使えればここまで複雑な処理を記述する必要はありません。しかし、INとEXISTSの挙動は同じでも「NOT INとNOT EXISTSの挙動は異なります」。結論から言うと、NOT INの場合はサブクエリの結果にNULLが入っていると1件もデータを取得できなくなります。(NOT EXISTSは単純にTrueとFalseが逆になるだけ)
実際に見てみましょう。まずDEPARTMENTテーブルにIDがNULLのレコードを追加し、このような形にします。
DEPARTMENT( - はNULL)
この状態でNOT INのSQLを実行してみましょう。
コード(NOT IN):
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID NOT IN
(SELECT ID FROM DEPARTMENT WHERE ID = 1 OR ID IS NULL)
結果:
部署IDが0, 2のレコードが出力されるのが直感的ですが、結果は1件も返却されませんでした。
このように、INとEXISTSは同じでも、NOT INとNOT EXISTSではNULLが入った場合の挙動が異なるので、NULLがNOT INの引数に入ってきてしまう場合はNOT EXISTSの使用を検討しましょう。
まとめ
サブクエリはテーブル結合と同じくSQL学習で挫折しやすい内容です。
特に相関サブクエリ初めは直感的に分かりづらく、頭が混乱する方も多いでしょう。
しかし、サブクエリ(特に相関サブクエリ)をうまく活用できるようになることで「痒いところに手が届くSQL」を書けるようになることは間違いありません。
ぜひ実戦でも活用に挑戦してみてください。
- カテゴリ:
- 【DB入門】RDBMS全般
- キーワード:
- データベース