050-5217-4037 お見積もり

開発者ブログ

2023年11月2日

【SQL】JOIN の仕組みを頭の中でイメージする

Skrumエンジニアの高田です。

今回は RDBMS(リレーショナルデータベース)の結合(JOIN)の仕組みを解説します。
RDBMS には PostgreSQL、MySQL、SQL Server などたくさんの種類がありますが、この記事で解説することはどの RDBMS にも共通していることです。

詳細設計書を書く時や他の人が書いたソースコード中の SQL を読み解く時など、その SQL が意味する動作を頭の中でイメージできる能力がとても重要になってきます。そしてこの SQL の動作を頭の中でイメージするという作業は設計・実装フェーズにおいて頻繁に発生する作業であり、ソースコードを書く能力と同等もしくはそれ以上に重要と言っても過言ではないと考えています。

この記事では RDBMS の SQL の動作をプログラム的な観点から精緻に説明するというよりは、SQL の動作を頭の中でイメージするという目的に絞ってコアの仕組みをシンプルに説明したいと思います。
( Skrum の社内 LT 会で用いた資料を使いながら説明します。)

1. テーマ選定理由

・結合を頭の中で正確にイメージできていないと、SQL を読むことはかろうじてできても設計ができない
・少し結合が複雑化すると対応できない
・期待したSELECT結果が返ってこない時に何が原因かわからない
・ゆえに原因調査も解決策も場当たり的(「ここをこうしたら、よくわからないけどとりあえず欲しい結果は返ってきた、、」)

2. SELECT の基礎知識

SELECT のデータの引っ張り方には 2 種類あります。

・フルスキャン(全表走査)
 → テーブル内の全てのレコードを上から1つ1つ確認し合致するレコードを取得

・インデックススキャン(索引走査)
 → インデックスを検索し目的のデータを見つける。それに紐づくテーブル内のレコードを直接取得

3. SELECT をイメージする

結合の前にまずは SELECT をイメージしてみましょう。

videos テーブルがあります。

下記の SQL が実行される時の RDBMS の処理フローはどうなるでしょうか?

SELECT video_id, category_id, title FROM videos WHERE title LIKE ‘半沢%’;

まず、フルスキャン or インデックススキャン のどちらで実行するかが判断されます。(この例ではこの後のフローはフルスキャンで実行されるものとする)

次に、結果テーブルに取得カラムが用意されます。

そして、videos テーブルの上から1つ1つ全レコードに対して、「title LIKE ‘半沢%’」条件に合致しているかをチェックしていき、合致したものは結果テーブルへ入ります。

まとめると、下記の流れになります。

1. フルスキャン or インデックススキャン のどちらで実行するかが判断される
2. 結果テーブルに取得カラムが用意される
3. videos テーブルの上から1つ1つ全レコードに対して、「title LIKE ‘半沢%’」条件に合致しているかをチェックしていく。合致したものは結果テーブルへ。

重要な点は、合致するレコードを取ってきてから必要なカラム以外を削除するのではなく、必要なカラムを最初に設定し合致したレコードは必要なカラムだけ取ってくるという点です。

「SELECT * FROM… とアスタリスクで書くのではなく、極力必要なカラムだけを指定するようにしましょう」とよく言われます。上記で説明した SELECT のイメージを理解していると、なぜこう言われるのか理由がよく理解できるでしょう。(一応答えを書いておくと、無駄なデータを引っ張ってくることによる速度低下と、結果テーブルが大きくなることによるメモリ利用量の増加を招きます。)

4. JOIN をイメージする

categories テーブルに videos テーブルを結合する場合を考えます。

下記の SQL が実行される時の RDBMS の処理フローはどうなるでしょうか?

SELECT * FROM categories c
JOIN videos v ON c.category_id = v.category_id
WHERE v.title LIKE ‘%の%’;

まず、結合テーブルに取得カラムが用意されます。

次に、FROM のテーブル(categories)の1番目のレコードが結合テーブルへ入ります。

そして、取得した categories レコードに合致する videos テーブルのレコードを拾ってきます。
これで結合テーブルの1行が完成します。

まとめると、下記の流れになります。

1. 結合テーブルに取得カラムが用意される
2. FROM のテーブル(categories)の1番目のレコードを結合テーブルへ
3. 2のレコードに合致する videos テーブルのレコードを拾ってくる

結合テーブルが1行完成した後は、2と3を繰り返します。

2と3の繰り返しを3回目まで見ていきましょう。

繰り返し2回目

まず、FROM のテーブル(categories)の2番目のレコードを結合テーブルへ入ります。

次に、取得した categories レコードに合致する videos テーブルのレコードを拾ってきます。

この時 videos レコードが複数取得された場合は、結合元のレコードはその数分だけコピーされます。

繰り返し3回目

まず、FROM のテーブル(categories)の3番目のレコードを結合テーブルへ入ります。

次に、取得した categories レコードに合致する videos テーブルのレコードを拾ってきます。

今回も videos レコードが複数取得されたため、結合元のレコードはその数分だけコピーされます。

繰り返し終了後

FROM のテーブル(categories)のレコードが一番下まで行き繰り返しが終了したら、結合テーブルを上から1つ1つ全レコードに対して、「v.title LIKE ‘%の%’」条件に合致しているかをチェックしていき、合致したものは結果テーブルへ入ります。

これで結果テーブルが完成し、結合処理と WHERE 句の処理が全て完了です。

再度全体をまとめると、下記の流れになります。

1. 結合テーブルに取得カラムが用意される
2. FROM のテーブル(categories)の1番目のレコードを結合テーブルへ
3. 2のレコードに合致する videos テーブルのレコードを拾ってくる
4. 結合テーブルを上から1つ1つ全レコードに対して、「v.title LIKE ‘%の%’」条件に合致しているかをチェックしていく。合致したものは結果テーブルへ。
(2と3は FROM のテーブルのレコード数分繰り返す)

重要な点は、下記です。
・SELECT から JOIN 句までで、まずは結合テーブルが作成される
・結合テーブルに対し、WHERE 句の条件が適用される

結合のイメージを正確にイメージすることができていれば、条件はなるべく WHERE 句ではなく JOIN 句で指定すると良い、という TIPS が得られます。理由は、SELECT 時に必要なカラムだけを指定する理由と同じで、結合テーブルが小さくなるためです。(WHERE よりも先に JOIN が実行されるため。)
上記はあくまで TIPS の1つです。頭の中にイメージができていれば TIPS を公式のように覚えなくても、どのような SQL が効率的かが自ずとわかってきます。また、SQL 構文エラーが出た際もなぜその SQL が無効なのかがイメージで理解できるようになると思います。

5. まとめ

SELECT 処理のイメージと結合処理のイメージを説明しました。

基本的にはこの2つがわかっていればほとんどの SQL の処理をイメージすることができます。UPDATE も SELECT のイメージと同じですし、相関サブクエリもサブクエリの中にメインクエリのテーブルの条件が入っているだけです。

SELECT 処理のイメージと結合処理のイメージを身につけておくだけで、SQL と対峙するときに SQL の処理を頭の中でイメージしながらスッキリしっかり理解できると思います。

Pagetop