LoginSignup
105
75

More than 5 years have passed since last update.

includes,joins,eager_load,preloadの違いを噛み砕いて説明する

Last updated at Posted at 2017-12-13

はじめに

自分がRailsエンジニアとしてインターンをしており、その中で直近で一番意味わからんな。と思ったことがこれです。自分の学習のためにもしっかりとまとめました。他の記事よりなるべく初心者にもわかりやすくを意識して書きました。

色々な人がこれについて記事を書いているので下の参考文献も合わせて確認ください。

そもそもこれどういう時使うの?

親子関係のtableにおいて親のものを呼び出す時に子のデータも一緒に呼び起こしてcontrollerの記述をスッキリできるものや、主にeach do文を使った時に発生するN+1問題を解決するためのものである。

ここではN+1問題の詳細については触れないが、ActiveRecordによるSQLの発行回数がめちゃくちゃ多くなってしまってえげつないことになるからとりあえず対処しなければならないものという認識で良い(?)。

でもこんなに種類があって、どれがどう優れていて、どう劣っているか。よくわからない時にこれを見て欲しい。

includes

とりあえずN+1問題の時はこれで解決するイメージだが、実際にどのようなSQLが働いているかというと

User.includes(:posts)

# =>
SELECT `users`.* FROM `users`
SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1, 2, 3, ...)

User.includes(:posts).where('posts.desc = "ruby is awesome"').to_a

# =>
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0,
       "posts"."title" AS t1_r1,
       "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3
FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
WHERE (posts.desc = "ruby is awesome")

ざっくりいうとpreloadの上位互換かつeager_loadとしても使える

preloadはどこが劣っているか

User.preload(:posts).where("posts.desc='ruby is awesome'")
を使うことができない。がincludesではそれを使うことができる。
つまりwhereで絞り込みができるpreload

どういった場合eager_loadとして使えるのか

先ほどのwhereを使った場合はeager_loadとして挙動する。(referencesする)
LEFT OUTER JOINの動きをする。

User.includes(:posts).where(name: 'User 1').where(posts: {title: 'Post 1-1'})
  SQL (0.2ms)  SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."author" AS t0_r2, "users"."created_at" AS t0_r3, "users"."updated_at" AS t0_r4, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."created_at" AS t1_r2, "posts"."updated_at" AS t1_r3, "posts"."user_id" AS t1_r4 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."name" = ? AND "posts"."title" = ?  [["name", "User 1"], ["title", "Post 1-1"]]

eager_load

正直、includesの説明と被ってしまうと思うが、includes+referenceの動きをする。
eager_loadは指定したアソシエーションをLEFT OUTER JOINで取得し、キャシュする。
また、preloadとは違いは、指定したテーブルに対しての絞り込み(where)が行える。

User.eager_load(:posts).to_a

# =>
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0,
       "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3
FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"

preload

上のincludesの説明を読んで欲しい。whereを使うことのできないincludes。
eager_loadと違って、JOINしたくない大きいテーブルを扱うときはpreloadを使うのがいい

User.preload(:posts).to_a

# =>
SELECT "users".* FROM "users"
SELECT "posts".* FROM "posts"  WHERE "posts"."user_id" IN (1)

joins

eager_load, preload との違いはキャッシュしない。(1度しかデータを使用しない)
joinsはinner joinを行うので、データはinner join的に捨てられる。
つまりデータのしぼりこみに使える。

User.joins(:posts).where(posts: { id: 1 })
# SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `posts`.`id` = 1

まとめ

これはActiveRecordのjoinsとpreloadとincludesとeager_loadの違いに書かれていた文ですが、そのテーブルとのJOINを禁止したいケースではpreloadを指定し、JOINしても問題なくてとりあえずeager loadingしたい場合はincludesを使い、必ずJOINしたい場合はeager_loadを使いましょう。という文があります。
この通りだなと思ったのでここに貼らせていただきました。

参考にした文献

ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
Preload, Eagerload, Includes and Joins
Making sense of ActiveRecord joins, includes, preload, and eager_load
preload, eager_load, includes, references, and joins in Rails

参考にした記事を書いた方々に感謝です。状態としては色々な記事の情報をまとめて理解しやすい言葉で説明した形になっていますが、今後、もっと理解できるようになったら修正したいと思います。

105
75
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
75