JOINとRailsのincludesメソッドの備忘録
はじめに
N+1を解消するincludesメソッドを使用したときの挙動について、動作について調べて確認したため雑に書き残しておきたいと思います。
本記事では、RailsのincludeメソッドとSQLのJOINに関して簡単に解説していきます。
includesメソッド
要約
includes
メソッドは、Active Recordを使用してデータベースから関連付けのあるデータ
をまとめて取得するために使用されます。
また、発生するN+1問題を解決することができます。
解説
includesを解説するにあたり前提として、UserモデルとPostモデルを用意します。
app/models/user.rb
class User < ApplicationRecord has_many :posts end
app/models/post.rb
class Post < ApplicationRecord belongs_to :user end
Userモデルには以下のレコードを保存します。
user1 = User.create(name: "User 1") user2 = User.create(name: "User 2")
Postモデルには以下のレコードを保存します。
post1 = Post.create(title: "Post 1", body: "Body 1", user_id: 1) post2 = Post.create(title: "Post 2", body: "Body 2", user_id: 2) post3 = Post.create(title: "Post 3", body: "Body 3", user_id: 1)
この状態で、Post.allを使用してPostモデルの全てのレコードを取得すると、以下のようになります。
user_idはPostモデルのデータのため取得できますが、Userモデルのレコードは取得しません。
[<Post id: 1, title: "Post 1", body: "Body 1", user_id: 1>, <Post id: 2, title: "Post 2", body: "Body 2", user_id: 2>, <Post id: 3, title: "Post 3", body: "Body 3", user_id: 1>]
Post.includes(:user)
とすることでPostモデルの全てのレコードとUserモデルのレコードをまとめて取得することができます。
[<Post id: 1, title: "Post 1", body: "Body 1", user_id: 1, user: <User id: 1, name: "User 1">>, <Post id: 2, title: "Post 2", body: "Body 2", user_id: 2, user: <User id: 2, name: "User 2">>, <Post id: 3, title: "Post 3", body: "Body 3", user_id: 1, user: <User id: 1, name: "User 1">>]
includeは N+1問題を解決する
上での解説からわかる通り、Post.includes(:user)
を使用することで、Postモデルのレコードに加えて、それぞれに紐づいているUserモデルのレコード
が含まれます。
includesメソッドを使用して関連付けされたデータもまとめて取得
することでeach文などの繰り返し処理を行う際に余分なクエリを発行して発生してしまうN+1問題を発生させずに済む
というメリットがあります。
includesを使わずにN+1問題を発生させる
以下のコードでは、User.all
メソッドで全てのユーザーを取得し、each
メソッドでそれぞれのユーザーに関連する投稿を取得しています。
以下の処理方法の場合、ユーザーの数だけSQLクエリが発行されてN+1問題が発生します。
users = User.all users.each do |user| posts = user.posts 略 end
上記のコードによって発行されるSQLクエリの例です。
SELECT * FROM users SELECT * FROM posts WHERE user_id = 1 SELECT * FROM posts WHERE user_id = 1 SELECT * FROM posts WHERE user_id = 2 SELECT * FROM posts WHERE user_id = 1 SELECT * FROM posts WHERE user_id = 3
以下の流れになります。
- 最初にすべてのユーザーを取得し、1行目のクエリが発行されます。
- 各ユーザーに対してそのユーザーの投稿を取得するために、各ユーザーに対して別々のクエリが発行されます。
includesを使用した場合のコード例とSQL文の例
繰り返し処理の中でUserに紐づくPostのレコード情報が欲しい場合は最初にincludesでpostのレコードも参照するようにします。
users = User.includes(:posts) users.each do |user| posts = user.posts 略 end
上記のコードによって発行されるSQLクエリの例です。
SELECT * FROM users SELECT * FROM posts WHERE user_id IN (1, 2, 3, 4, 5)
- 最初にすべてのユーザーを取得し、1行目のクエリが発行されます。
- IN句を使ってそれぞれのuser_idに該当する投稿情報を一度に取得しています。
これによりN+1問題が発生せずに全てのユーザー情報と投稿情報を取得することができます。
JOIN (SQL文)
JOINは、2つ以上のテーブルを結合して、一つのテーブルのように扱うことができます。
Active RecordのクエリメソッドであるincludesメソッドではJOINが用いられています。
PostとUserのレコードをJOINするSQL文の例です。
SELECT * FROM posts JOIN users ON posts.user_id = users.id;
Postモデルのテーブルであるpostsテーブルと、UserモデルのテーブルであるusersテーブルをJOINしています。
上記のSQL文によって取得される結果の例です。
[ { id: 1, title: "Post 1", body: "Body 1", user_id: 1, created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00", id: 1, name: "User 1", created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00" }, { id: 2, title: "Post 2", body: "Body 2", user_id: 2, created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00", id: 2, name: "User 2", created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00" }、 { id: 3, title: "Post 3", body: "Body 3", user_id: 1, created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00", id: 1, name: "User 1", created_at: "2023-04-01 00:00:00", updated_at: "2023-04-01 00:00:00" } ]
JOINによって、postsテーブルとusersテーブルのレコードを結合した結果が取得されています。
また、レコードの重複も解消されてUserモデルの情報を含んだPostモデルのレコードが取得されています。
includeメソッドとJOINの関係性についても深ぼってみました。
Active Recordはやっぱり便利ですね。
おわりに
コメント
本記事の内容は以上になります!
とても初歩的な内容でしたが、発行されるクエリや生成されるオブジェクトまで頭にいれておけると良いと思いました。
少しでも参考になったり学びのきっかけになりますと幸いです。
間違いがありましたら修正いたしますので、ご指摘ください。
興味があれば他の記事も更新していきますので是非ご覧になってください♪
◇ プログラミングスクールのご紹介 (卒業生より)
お世話になったプログラミングスクールであるRUNTEQです♪
ご不明な点ありましたらお気軽にコメントか、TwitterのDMでお答えします♪