- Rails - INNER JOIN で eager loading - Qiita
- Rails - ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita
- ruby on rails - How to write left join on( ~~ and ~~). not left join on ~~ where ~~ in ActiveRecord - Stack Overflow
発行されているSQLは下記を引用:
Rails - ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita
eager_load
User.eager_load(:posts)
SELECT `users`.`id` AS t0_r0,
`users`.`name` AS t0_r1,
`users`.`created_at` AS t0_r2,
`users`.`updated_at` AS t0_r3,
`posts`.`id` AS t1_r0,
`posts`.`user_id` AS t1_r1,
`posts`.`created_at` AS t1_r2,
`posts`.`updated_at` AS t1_r3
FROM `users`
LEFT OUTER JOIN `posts` ON `posts`.`user_id` = `users`.`id`
User.eager_load(:posts).where(posts: { id: 1 })
SELECT `users`.`id` AS t0_r0,
`users`.`name` AS t0_r1,
`users`.`created_at` AS t0_r2,
`users`.`updated_at` AS t0_r3,
`posts`.`id` AS t1_r0,
`posts`.`user_id` AS t1_r1,
`posts`.`created_at` AS t1_r2,
`posts`.`updated_at` AS t1_r3
FROM `users`
LEFT OUTER JOIN `posts` ON `posts`.`user_id` = `users`.`id`
WHERE `posts`.`id` = 1
テーブルを結合して1発でもってくる。
テーブルデータがでかくて結合するのがしんどいやつはeager_load
ではしんどいと思われる。
さらに重要な点としては結合しているので、has_many
な関係と結合するとsum
計算するときに結合してできたぶんのレコードを重複して足し合わせてしまう場合があるかもしれない。
belongs_to
な関係を取得したい(親を取得したい)場合はeager_load
にするとSQLの発行回数がpreloadに比べて減らせてうれしいという発想もある。
preload
User.preload(:posts)
SELECT `users`.*
FROM `users`
SELECT `posts`.*
FROM `posts`
WHERE `posts`.`user_id` IN (1, 2, 3, ...)
User.preload(:posts).where(posts: { id: 1 })
preloadをwhereで絞り込むとRoRがダメ(できない)っていう。
SELECT `users`.*
FROM `users`
WHERE `posts`.`id` = 1 # => Mysql2::Error: UNKNOWN COLUMN 'posts.id' IN 'where clause': SELECT `users`.* FROM `users` WHERE `posts`.`id` = 1
さらに重要な点としてはeager_load
と違ってSQLは2回発行されるが、結合していないので、sum
計算で結合によって生じた(そもそも結合しないからね)重複分を足し合わせてしまう心配がない。
has_many
な関係を取得したい(子を取得したい)場合はpreload
にするとeager_load
だと自分自身が重複してメモリにとられてしまうぶんのメモリを効率的につかえるという発想もある。
includes
空気を読んで、eager_loadの挙動かpreloadの挙動に変わる。
User.includes(:posts).where(posts: { id: 1 })
と書くと、preload
はwhere
で絞り込めないので、eager_load
の動きになる(空気読んでる)。
まとめ
テーブル結合してもメモリとか大丈夫そうだとeager_load。でも集計関数などで結合重複ぶんを数えてしまいそうなときは注意。
テーブル結合してメモリとかヤバそうだとSQLの発行回数増えるけど、preload。さらに集計関数などで重複ぶんを数えてしまうことがないので(結合しないからそもそも重複しない)扱いやすい。
追加で**joins**と**merge**も。
ここからはほぼ
Rails と テーブル結合 - Qiita
の該当部分の引用となります。
joins
eager_load, preload と違いキャッシュしない。
1回こっきり使用するクエリーの時に使用と思われる。
joinsはinner joinを行うので、データはinner join的に捨てられる。
つまりデータのしぼりこみに使える。
merge
joins で結合したテーブルの条件式に、ActiveRecord::Relationを使用することができる。
User.joins(:avatar).merge(Avatar.where(id: 1))
=>
SELECT users.* FROM users INNER JOIN avatars ON avatars.resource_id = users.id AND avatars.resource_type = 'User' WHERE avatars.id = 1
# 普通に書いた場合
User.joins(:avatar).where('avatars.id = 1')
or
User.joins(:avatar).where(avatars: {id: 1})