RailsのActiveRecordで関連データを扱いたいとき、joins
や includes
など、いろいろなメソッドがありますよね。
でも、どう使い分けたらいいのかよく分からない…と疑問に思う方も多いのではないでしょうか。
この記事では、そのような初心者向けに 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 |
コメント