0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】joins・preload・eager_load・includesの違いと使い分け

Posted at

RailsのActiveRecordで関連データを扱いたいとき、joinsincludes など、いろいろなメソッドがありますよね。

でも、どう使い分けたらいいのかよく分からない…と疑問に思う方も多いのではないでしょうか。

この記事では、そのような初心者向けに joins / preload / eager_load / includes の違いと使い分け方をまとめました。

前提

この記事でのモデル関係は以下の通りです。

class Book < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :books
end

メソッドの違い

後述しますが、joinsは主にテーブルを結合する目的で、preload・eager_load・includesは主に関連付けをeager loading(一括読み込み)する目的で使われます。

joins

books = Book.joins(:author)
SELECT books.* FROM books
INNER JOIN authors ON authors.id = books.author_id;

関連テーブルを INNER JOIN します。

関連付けの一括読み込みはされないので、book.author を呼ぶと毎回SQLが発行されるため、N+1問題が起きます。

books.each do |book|
  puts book.author.name  # ← N回クエリが発行される(N+1問題)
end

つまり joinsは、関連テーブルを where などで使うことができるが、N+1問題に注意が必要です。

※なお、関連テーブルを INNER JOIN ではなく LEFT OUTER JOIN する場合は、left_outer_joins メソッドを使うこともできます。

preload

books = Book.preload(:author)
-- 本のデータを取得
SELECT * FROM books;

-- 取得した本のデータの author_id(例: 1, 2)に該当する著者を取得
SELECT * FROM authors WHERE id IN (1, 2);

テーブル結合せず、メインのテーブルと関連テーブルごとに別クエリを発行します。

関連付けの一括読み込みがされるので、book.author を呼んでもN+1問題は発生しません。

books.each do |book|
  puts book.author.name  # ← 追加のSQLは発行されない
end

つまり preloadは、関連テーブルを where などで使えないが、N+1問題は発生しません。

eager_load

books = Book.eager_load(:author)
SELECT
  books.id AS t0_r0,
  books.title AS t0_r1,
  books.author_id AS t0_r2,
  authors.id AS t1_r0,
  authors.name AS t1_r1
FROM books
LEFT OUTER JOIN authors ON authors.id = books.author_id;

関連テーブルを LEFT OUTER JOINする。

関連付けの一括読み込みがされるので、book.author を呼んでもN+1問題は発生しません。

books.each do |book|
  puts book.author.name  # ← 追加のSQLは発行されない
end

つまり eager_loadは、関連テーブルを whereなどで使うことができ、N+1問題も発生しません。

includes

books = Book.includes(:author)

Railsが内部的に preload または eager_load を自動選択します。

ただし、includesの自動選択は意図しないクエリになることもあるため、できれば eager_load や preload を明示するのが望ましいと考えられます。

joinsやpreloadを使うメリットは?

一見、eager_load だけで良さそうに思えるかもしれません。
でも、joins や preload にもシンプルさやパフォーマンスの観点からのメリットがあります。

joins を使うメリット

検索条件にだけ関連テーブルを使いたいときに最適

例:Book.joins(:author).where(authors: { name: "夏目漱石" })
book.author.name など関連付けは使わないけど、関連テーブルで絞り込みたいだけならこれで十分です。

無駄なデータを取らないため軽量

eager_load のように全カラムを取得するのに比べ、joins は books.* だけ取得できるため、SQLがシンプルで速いことがあります。

preload を使うメリット

N+1を防ぎつつ、JOINを避けられる

たとえば、JOINによって結果セットが大きくなりそうな場合は、1本のクエリで全てを取得するよりも、preload を使ってクエリを分けた方がパフォーマンスが良くなることがあります。

JOINによる副作用を避けたいとき

JOINを使わず別クエリで関連データを取得して紐付けるため、JOINによる副作用(例えば関連レコードがない場合でもNULL行が混ざるなど)を避けることができます。

基本的な使い分けの目安

状況 選ぶメソッド
関連テーブルで絞り込みをするが、関連付けは使わない joins
関連テーブルで絞り込みをしないが、関連付けは使う preload
関連テーブルで絞り込みをするし、関連付けも使う eager_load
Railsに自動判定させたい includes
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?