はじめに
この仕事に携わっていると、結構な経験者でもSQL、特にテーブル同士の結合が苦手という人によく会います。
で、話を聞くと苦手な理由はテーブル同士のリレーションを視覚的にイメージ出来ないことにあるのかと。。
かつて自分もSQLは大の苦手でした。
2、3年目の時に某電力の大規模Javaプロジェクトに放り込まれました。
テーブル同士の多重度が多対多、オンラインPGで1SELECTあたり3000〜5000行という変態的なSQL。
MVCモデルが出始めの頃、Webフレームワークなんて言葉が流行り出す前
バックエンドからフロントエンドまで全てをベタ書き。
期日までに徹夜・休出しながら直したり書かないといけなかったものですから、それはもう大変な苦労と挫折を味わいました。
そんな環境下でテーブル同士のリレーションを図に整理しながら、書いたり、手直ししたりする内に、視覚的にイメージ出来るようになり、SQLへの苦手意識はほとんど無くなりました。
「乗り越えた壁はいつしか自分を守る盾になる」と誰かが言ってたアレです(笑)
SQLが苦手な方向けの説明になってます。
ある程度のSQL経験者には退屈な内容になっておりますので、あしからず。
3つのJOIN
INNER JOIN
・LEFT (OUTER) JOIN
・FULL (OUTER) JOIN
の3つのJOINについて説明します。
※他にもRIGHT (OUTER) JOIN
、CROSS JOIN
等があります。
ただし、RIGHT (OUTER) JOIN
はLEFT (OUTER) JOIN
をただ左右反転させたもの、CROSS JOIN
はよっぽど切羽詰まった状況使うか、使う時点で処理フローを変えることを検討した方が良い、どちらも実務で使うことは稀なので割愛します。
INNER JOIN
説明するまでもありませんが、最も一般的でよく使われるJOINですね。
内部結合
っていわれるやつです。
仮にA
とB
というテーブルをINNER JOIN
した場合の関係性をベン図に表すとこんな感じです。
「テーブルA
にも存在し、かつ、テーブルB
にも存在するレコードを取ってくる」とゆうJOINです。
真ん中の赤く重なり合ってるところをとります。
テーブルA・Bがこんな状態だったとします。
- テーブルA
PK_A | VAL_A |
---|---|
001 | AAA |
002 | BBB |
004 | CCC |
005 | DDD |
007 | EEE |
009 | FFF |
- テーブルB
PK_B | VAL_B |
---|---|
001 | GGG |
003 | HHH |
006 | III |
008 | JJJ |
009 | KKK |
PK_A
・PK_B
がそれぞれの主キーです。
A
のPK_AとB
のPK_BでJOINします。
SELECT
PK_A,
VAL_A,
PK_B,
VAL_B
FROM
A
INNER JOIN B ON
A.PK_A = B.PK_B
結果は
PK_A | VAL_A | PK_B | VAL_B |
---|---|---|---|
001 | AAA | 001 | GGG |
009 | FFF | 009 | KKK |
ですね。
A
・B
両方に存在する001
と009
のみが抽出されます。
LEFT OUTER JOIN
これもよく使われるJOINです。
左外部結合
っていわれます。
たまにレガシーなところ行くといまだに(+)
なんて記述もありますが、アレは古いOracle
の方言です。
可読性・保守性が著しく低下するので間違っても(+)
を記述するのはやめておきましょう。
また、OUTER
は省略可能ですが、INNER JOIN
との違いを明確にさせるために、あえて省略しないで説明します。
ベン図に表すとこんな感じです。
「テーブルA
の全てプラス、テーブルA
と重なり合うテーブルB
のレコードを取ってくる」とゆうJOINです。
先ほどと同じようにA
のPK_AとB
のPK_BでJOINします。
SELECT
PK_A,
VAL_A,
PK_B,
VAL_B
FROM
A
LEFT OUTER JOIN B ON
A.PK_A = B.PK_B
結果は
PK_A | VAL_A | PK_B | VAL_B |
---|---|---|---|
001 | AAA | 001 | GGG |
002 | BBB | null | null |
004 | CCC | null | null |
005 | DDD | null | null |
007 | EEE | null | null |
009 | FFF | 009 | KKK |
A
の全件と、A
のキーと一致するB
が取得されていることがわかります。
A
のキーと一致しないB
の歯抜けの部分についてはnull
が埋め込まれていますね。
FULL OUTER JOIN
完全外部結合
と言われます。
実務で使われる頻度は少ないですが、覚えておいて損はないです。
A
とB
を重なり合う部分も含めて全件取ってきます。
SELECT
PK_A,
VAL_A,
PK_B,
VAL_B
FROM
A
FULL OUTER JOIN B ON
A.PK_A = B.PK_B
結果は
PK_A | VAL_A | PK_B | VAL_B |
---|---|---|---|
001 | AAA | 001 | GGG |
002 | BBB | null | null |
null | null | 003 | HHH |
004 | CCC | null | null |
005 | DDD | null | null |
null | null | 006 | III |
007 | EEE | null | null |
null | null | 008 | JJJ |
009 | FFF | 009 | KKK |
A
・B
の全件が取得されていることがわかります。
そしてお互いの歯抜けの部分についてはnull
が埋め込まれていますね。
こんなんどうでしょう?
ここまでお読み頂き、JOINの視覚的なイメージが着いたでしょうか?
「テーブルB
と重なり合わないテーブルA
のレコードを取ってくる」とゆうJOIN、、
もちろん、そんなJOINありません。
ではどうすれば良いか?
SELECT
PK_A,
VAL_A,
PK_B,
VAL_B
FROM
A
LEFT OUTER JOIN B ON
A.PK_A = B.PK_B
WHERE
B.PK_B IS NULL
A
とB
をLEFT OUTER JOIN
させてから、
WHERE
句でB
の主キーPK_B
がNULL
のレコードを指定しています。
結果は
PK_A | VAL_A | PK_B | VAL_B |
---|---|---|---|
002 | BBB | null | null |
004 | CCC | null | null |
005 | DDD | null | null |
007 | EEE | null | null |
歯抜けレコードを利用するのがポイントです。
前述のLEFT OUTER JOIN
の結果からB
の歯抜けレコードを指定することで
A
・B
両方にある001
・009
のレコードが除外され、
B
と重なり合わないA
のレコードを取得できます。
これが「歯抜け」という文字をあえて太字にした理由です。
じゃあ、こんなのも
理屈が分かっちゃえば、こんなのも簡単ですね。
「重なり合わない部分の`テーブルA`と`テーブルB`のレコードを取ってくる」 同じように**歯抜け**レコードをうまく使いましょう。SELECT
PK_A,
VAL_A,
PK_B,
VAL_B
FROM
A
FULL OUTER JOIN B ON
A.PK_A = B.PK_B
WHERE
A.PK_A IS NULL
OR B.PK_B IS NULL
結果は
PK_A | VAL_A | PK_B | VAL_B |
---|---|---|---|
002 | BBB | null | null |
null | null | 003 | HHH |
004 | CCC | null | null |
005 | DDD | null | null |
null | null | 006 | III |
007 | EEE | null | null |
null | null | 008 | JJJ |
前述のFULL OUTER JOIN
の結果からA
またはB
の歯抜けレコードを指定することによって
A
・B
両方にある001
・009
のレコードが除外され、
お互い重なり合わないA
・B
のレコードを取得できます。
さいごに
最近主流のアーキテクチャでは(バッチ処理を除いて)複雑なクエリを書くのはアンチパターンです。
なので上記のようなクエリを組む事も稀かと思いますが、テーブル同士を比較してアンマッチリストを抽出したい時や、ゴミデータ・不正データを調査したい時などにJOINを使いこなせると色々と便利です。
あと気をつけなくてならないのはSELECT
文はF・W・G・H・S・O
の順番に評価されます。
何かというとFROM
・WHERE
・GROUP BY
・HAVING
・SELECT
・ORDER BY
の頭文字です。
JOIN
はFROM
句中で書くため、一番最初に評価されます。
なので大量データを保持したテーブル同士をJOINさせるとパフォーマンスの劣化に繋がったりします。
サブクエリやWITH
句を使って比較に必要なデータに絞ってからJOINさせる等のパフォーマンスの考慮は必要です。
ただし、最近はOracle
・MySQL
のオプティマイザによって実行計画が最適化されるので、トンチキなSQLを書いても大体は動いてくれます。
自分が走り出しの頃は変なSQL書いたら幾度となく先輩にブッ◯されそうになりました(笑)
で、自分が言いたいのはテーブル同士のリレーションを視覚的にイメージできれば、どんなSQLがやって来ても怖くない、
何千行というSQLも結局はテーブルのJOINの組み合わせに過ぎないということです。
最後までお読み頂きありがとうございました。