Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

自分が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

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

ostk0069
Swift / Rails Engineer https://www.wantedly.com/users/31623094
https://github.com/takumaosada
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした