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

Ruby on Rails
記事内に広告が含まれています。

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

最後までお読みいただきありがとうございました。

皆さんからのコメントやSNSでのシェア、嬉しい投稿をいただくと本当に励みになります。

もしこの記事が気に入ったら感想をコメントやSNSでシェアしていただけると嬉しいです。

皆さんの声を聞かせてくださいね!

Ruby on Rails
tetsuをフォローする
簿記はじめるってよ

コメント

タイトルとURLをコピーしました