よくあるpreload, includes, eager_loadの違いについて実際に打ってみて確認してみた。今回はuserレコードがtaskレコードに対して1対多のERにおいてuserとそれに紐づくtaskを取得するようなクエリの挙動を確認します。
クエリ2回に分けられるパターン
User.includes(:tasks)
User.preload(:tasks)
実際のクエリはこんな感じでusersのID配列でtasksを検索している
User Load (3.6ms) SELECT "users".* FROM "users"
Task Load (0.7ms) SELECT "tasks".* FROM "tasks"
WHERE "tasks"."user_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
JOINしてくれるパターン
User.eager_load(:tasks)
User.includes(:tasks).references(:tasks)
User.includes(:tasks).where(tasks: { completed: false })
SELECT句がワイルドカード記述ではなくなり、LEFT OUTER JOINしてくれます。
SELECT "users"."id", ..., "tasks"."id", ... FROM "users"
LEFT OUTER JOIN "tasks" ON "tasks"."user_id" = "users"."id"
WHERE句にカラムねーよ、って言われるパターン
以下のようにincludesを使ったJOINでwhere句にハッシュを使わない場合に、includesしたテーブルのカラムを条件にするとエラーになります。User.includes(:tasks).where("tasks.completed = false")
SQLite3::SQLException: no such column: tasks.completed:
SELECT "users".* FROM "users" WHERE (tasks.completed = false)
where句がハッシュの場合は色々と空気を読んでLEFT OUTER JOINしてくれるけど、文字列はWHERE句にそのまま入れるだけっぽいです。クエリ2回パターンと同様に、usersテーブルを取得しようとした際にtasksのカラムで絞込をしようとして、エラーになっています。