1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

railsのテーブル結合メソッドの使い分け

Last updated at Posted at 2023-10-07

はじめに

テーブルを結合するための複数あるメソッドの使い分けがあやふやなのでここで調べたことなどをまとめます。

得られる結果が同じだからと言って意識しないでいると思わぬところで足を掬われそう。

joins

結合先のテーブルの情報を使わない時に使う。→メモリ削減
内部結合INNER JOINを行う。

  • 結合先のテーブルで絞り込むことが可能
  • 結合先のテーブルの情報を取得できない
joins.rb
User.joins(:user_profiles)
# 発行されるSQL
SELECT "users".* FROM "users" INNER JOIN "user_profiles" ON "user_profiles"."user_id" = "users"."id"

preload

結合をしたくない時に使用する→テーブルの規模が大きく、動作が極めて重くなる場合(has_many関係)や、LEFT JOIN時のnullを発生させたくない場合など
SQLを二回発行し、擬似的にJOINしてデータを取得する。

  • 結合先のテーブルで絞り込むことが不可能→例外が発生する。
  • 結合先のテーブルの情報を取得できない
preload.rb
User.preload(:user_profiles)
# 発行されるSQL
SELECT "users".* FROM "users"

SELECT "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" IN (1,2,..)

eager_load

外部結合をさせたい時に使用する→他のメソッドと違い、必ず外部結合したクエリ1つを発行するため、効率的かつ早い。(has_one、belongs_to関係に有効)
外部結合LEFT OUTER JOINを行う。

  • 結合先のテーブルで絞り込むことが可能
  • 結合先のテーブルの情報を取得できる
eager_load.rb
User.eager_load(:user_profiles)
# 発行されるSQL
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "user_profiles"."id" AS t1_r0,
       "user_profiles"."address" AS t1_r1, "user_profiles"."user_id" AS t1_r2, "user_profiles"."tel" AS t1_r3
FROM "users" LEFT OUTER JOIN "user_profiles" ON "user_profiles"."user_id" = "users"."id"

includes

preloadとeager_loadのどちらかの挙動を行う。→JOINしても問題ない場合に使う。
結合先のテーブルを絞り込もうかするかどうかで挙動を変える。
※関連テーブルが複数になる場合、個別に挙動を指定できない点は注意する。

  • 結合先のテーブルで絞り込まない場合はpreloadと同じ挙動
includes.rb
User.includes(:user_profiles)
# 発行されるSQL
SELECT "users".* FROM "users"

SELECT "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" IN (1,2,..)
  • 結合先のテーブルで絞り込む場合はeager_loadと同じ挙動
includes.rb
User.includes(:user_profiles)where "user_profiles.address = 'Tokyo'").
# 発行されるSQL
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "user_profiles"."id" AS t1_r0,
       "user_profiles"."address" AS t1_r1, "user_profiles"."user_id" AS t1_r2, "user_profiles"."tel" AS t1_r3
FROM "users" LEFT OUTER JOIN "user_profiles" ON "user_profiles"."user_id" = "users"."id" WHERE (user_profiles.add = "Tokyo")

incledesの取り扱いについて

preloadとeager_loadを内包しているため、一見良さそう。
しかし、状況に応じて挙動が変わる上にコントロールできないため、思わぬ不具合につながるかも?

includesを使わないべきという資料も複数見つけられた

includesはクエリが状況によって変わってコントロールしずらいので基本使わないようにしています。

引用: ActiveRecordのincludes, preload, eager_load の個人的な使い分け

includesはなるべく利用しない方が良い(理由は後述します)
理由:意図しない挙動を防ぐため

引用: ActiveRecordのincludesは使わずにpreloadとeager_loadを使い分ける理由

判断が明確にできる場合はなるべくpreloadeager_loadを使う方がよいが、後の改修を考えたらどちらの挙動も取れるincludesを使うメリットもあるといえる。

あとは、preloadjoinsも)は結合先のテーブルのデータを参照したり絞り込みに使えないため、結合先のデータを使用したいかどうかが判断の一つの基準になりそう。

モデルなどにメソッドとして共通化する場合に使用する際は特に気をつけたい

  • preloadeager_load
    挙動を完全に決定できるが、その分メソッド自体の汎用性、テーブルのデータ量の移り変わりに弱い

  • incledes
    結合先のデータを参照できるほか、テーブルのデータ量にあわせてエンジニア側で挙動が変わるようにするなど、うまく使うことができれば汎用性に優れるといえる。ただし、メソッドの使用先の状況で挙動が変わるというのは管理やコントロールが困難になる可能性が大きい。

参考

https://zenn.dev/tomoya_pama/articles/85a37b3f1e6119
https://zenn.dev/mithuami/articles/c4b0e9694182d1
https://zenn.dev/d0ne1s/articles/fdf4832d478da6
https://zenn.dev/kibe/articles/c043a7807fa7d0
https://moneyforward-dev.jp/entry/2019/04/02/activerecord-includes-preload-eagerload/
https://qiita.com/ryosuketter/items/097556841ec8e1b2940f

1
3
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
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?