joinsとincludesの違いを分かっている奴どんくらいいるの?

はじめに

Rubyを扱っていれば一度くらいはDB操作でjoinsincludesといったメソッドを使うことがあるかと思います。この2つのメソッドは意外と奥が深くてそれ故に理解するのが少しだけ難しい側面があります。久しぶりにjoinsincludesを使おうとしたのですが完全に使い方を忘れていて調べ直すはめになってしまいました。そこで今後繰り返し調べることがないように今回まとめておきたいと思います。

N+1問題について

まず用途についてですがN+1問題を回避する為に使うことが多いです。N+1問題とはループの中でDB操作を記述してしまい、結果クエリを大量に(ループ回数分)発行してしまうことです。

例えばAモデルとBモデルに関連があるとしてAテーブル全体に対してeachで一つずつ処理をするとします。そして関連のあるBテーブルのレコードを検索してA・Bそれぞれの情報を出力する。これをAテーブルのレコード分繰り返します。

↑は典型的なN+1問題が起こるパターンです。クエリをAテーブルから全件取得する1回と、Aテーブルのレコード数分Bテーブルから関連のレコードを検索するので合計(N+1)回のクエリを無駄に発行しています。レコードが多いほどパフォーマンスが低下してしまいます。これを回避するのにjoinsincludesを使います。

joinsメソッドについて

まずはjoinsメソッドについて見てみます。仮にAとBに関連があるとしてA.joins(:B)と実行したとします。この場合SQL的には次のようなクエリが実行されています。

※A、Bそれぞれのカラムは適当ですが関連を持つためにBがa_idを持っていると想定して下さい。
SELECT A.* FROM A INNER JOIN B ON B.a_id = A.id

所謂内部結合のためのINNER JOINが行われています。しかしここで気をつけなければいけないことが2点あります。1つ目は内部結合のための処理が行われているけれど返却値は内部結合ではないということです。

SELECTの直後に注目してみるとA.*となっており*となっていません。つまりBのカラムは返却されていないということです。これは厳密には内部結合した状態からAのカラムのみを返却しているということです。SQLに慣れている人からすると逆に落とし穴にハマりやすいポイントなので注意して下さい。

2つ目は単純にSQLの結果が配列に格納されて返却されないということです。どういうことかと言うとjoinsは関連データが解決されたActiveRecord_Relationクラスの値が返却されるということです。レコードを取り出せばそれらはモデルインスタンスとしてActiveRecordクラスで定義されているメソッドを普通に使うことが出来ます。

2つ目の内容があるから結果的にjoinsは内部結合とイコールに見えますが、厳密には内部結合と同じ様なものと捉えた方が後々混乱することがないと思います。

includesのeager_loadとpreloadについて

includesは場合によって自動でeager_loadを実行するかpreloadを実行するかよしなに振り分けるメソッドです。eager_loadpreload はどちらも関連を取得してキャッシュしてくれるものですが取得方法が異なります。まずはeager_loadpreloadの単体での動作を確認します。

A.eager_load(:B)と実行した場合はLEFT OUTER JOINが実行されます。つまり外部結合が行われます。こちらも返却値はActiveRecord_Relationクラスのオブジェクトなので注意が必要です。

続いてA.preload(:B)と実行した場合は以下のSQLが実行される。

SELECT A.* FROM A
SELECT B.* FROM B WHERE B.a_id IN (1,2,...)

↑を見ると2回クリエが実行されているのが分かります。まずはAテーブルから全件を取得し、そのidとB.a_idが一致するBテーブルのレコードを取得しています。複数のクエリを使っている点は異なるけれどもアソシエーションを解決している点ではjoinsと変わりません。そのため大きいテーブルに対してはpreloadを使い、JOINするテーブルのデータを使わない場合はjoinsを使うというのが一般的な使い分けらしいです。

終わりに

正直未だに理解が不十分な箇所もあります。例えばincludesがどうやってeager_loadpreloadを振り分けているのかなど理解しきれませんでした。ですが取り敢えずそれぞれが何をしているのかを把握出来ただけでも今回は良しとしたいと思います。ORマッパーという巨人の肩に乗っかるのは楽でいいですね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA