Rails:include対:joins


345

これは、「これを行う方法がわからない」という質問ではなく、「なぜこのように機能するのか」という質問です。

したがって、使用することがわかっている関連レコードをプルするという福音は:include、結合を取得して余分なクエリの全体を回避するために使用することです。

Post.all(:include => :comments)

ただし、ログを見ると、結合は行われていません。

Post Load (3.7ms)   SELECT * FROM "posts"
Comment Load (0.2ms)   SELECT "comments.*" FROM "comments" 
                       WHERE ("comments".post_id IN (1,2,3,4)) 
                       ORDER BY created_at asc) 

すべてのコメントを一度にプルするのでショートカットを取っていますが、それまだ結合ではありません(すべてのドキュメントが言っているようです)。結合を取得できる唯一の方法は、次の:joins代わりに使用することです:include

Post.all(:joins => :comments)

そしてログは示しています:

Post Load (6.0ms)  SELECT "posts".* FROM "posts" 
                   INNER JOIN "comments" ON "posts".id = "comments".post_id

何か不足していますか?私は6ダースの関連付けを持つアプリを持っていて、1つの画面にそれらすべてのデータを表示します。6人の個人ではなく、1つの結合クエリを使用する方がよいようです。パフォーマンスに関しては、個々のクエリよりも結合を行う方が常に良いとは限らないことを知っています(実際に時間を費やすと、上記の2つの個々のクエリが結合よりも高速であるように見えます)。私は読んでいますが:include、宣伝どおりに機能していないことに驚いています。

たぶんRails パフォーマンスの問題を認識していて、特定の場合を除いて参加しませんか?


3
古いバージョンのRailsを使用していた場合は、タグまたは質問の本文にその旨を記載してください。それ以外の場合で、Rails 4 NOWを使用している場合、それはincludes(これを読んでいる人にとって)
onebree

また、今そこにある:プリロードと:eager_loadのblog.bigbinary.com/2013/07/01/...
CJW

回答:


179

と思われる:include機能は、Railsの2.1に変更しました。Railsはすべてのケースで結合を行っていましたが、パフォーマンス上の理由から、状況によっては複数のクエリを使用するように変更されました。 ファビオ秋田によるこのブログ投稿には、変更に関するいくつかの良い情報があります(「最適化された積極的な読み込み」というタイトルのセクションを参照してください)。



これは非常に役に立ちます。ありがとうございます。それを必要とする「場所」がなくてもRailsに参加を強制する方法があったことを願っています。場合によっては、結合がより効率的になり、重複のリスクが発生しないことがわかります。
Jonathan Swartz 2013年


@JonathanSwartz新しいバージョンのRailsは、eagerloadを使用してこれをサポートしているようです。リンクをありがとうNathanLong
rubyprince

92

.joinsテーブルを結合し、選択したフィールドを返します。結合クエリ結果で関連付けを呼び出すと、データベースクエリが再度起動されます

:includes含まれている関連付けを積極的に読み込み、メモリに追加します。:includes含まれているすべてのテーブル属性をロードします。インクルードクエリの結果で関連付けを呼び出すと、クエリは実行されません


71

ジョインとインクルードの違いは、インクルードステートメントを使用すると、はるかに大きなSQLクエリが生成され、他のテーブルからすべての属性がメモリに読み込まれることです。

たとえば、コメントでいっぱいのテーブルがあり、:joins => usersを使用してすべてのユーザー情報を並べ替えの目的で取得する場合などは、正常に機能し、:includeよりも時間がかかりませんが、表示したいとします:namesを使用して情報を取得するには、フェッチするユーザーごとに個別のSQLクエリを作成する必要がありますが、:includeを使用した場合は、この情報を使用できます。

良い例:

http://railscasts.com/episodes/181-include-vs-joins


55

最近、レール:joins:includesレールの違いについてもっと読んでいました。これが私が理解したことの説明です(例:))

このシナリオを考えてみましょう:

  • ユーザーhas_manyコメントとコメントbelongs_toユーザー。

  • Userモデルには次の属性があります:Name(string)、Age(integer)。Commentモデルには次の属性があります:Content、user_id。コメントの場合、user_idはnullにすることができます。

参加:

:joinsは、2つのテーブル間の内部結合を実行します。したがって

Comment.joins(:user)

#=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first   comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">, 
     #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,    
     #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>

user_id(コメントテーブルの)がuser.id(usersテーブル)と等しいすべてのレコードをフェッチします。したがって、あなたがするなら

Comment.joins(:user).where("comments.user_id is null")

#=> <ActiveRecord::Relation []>

次のように空の配列を取得します。

さらに、joinは結合されたテーブルをメモリにロードしません。したがって、あなたがするなら

comment_1 = Comment.joins(:user).first

comment_1.user.age
#=>←[1m←[36mUser Load (0.0ms)←[0m  ←[1mSELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1←[0m  [["id", 1]]
#=> 24

ご覧のとおりcomment_1.user.age、結果を取得するために、バックグラウンドでデータベースクエリを再度実行します

含まれるもの:

:includesは、2つのテーブル間で左外部結合を実行します。したがって

Comment.includes(:user)

#=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
   #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
   #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,    
   #<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

結果として、commentsテーブルのすべてのレコードが結合されたテーブルになります。したがって、あなたがするなら

Comment.includes(:user).where("comment.user_id is null")
#=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

示されているように、comments.user_idがnilであるレコードをフェッチします。

さらに、メモリに両方のテーブルをロードします。したがって、あなたがするなら

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

ご覧のとおり、comment_1.user.ageは、バックグラウンドでデータベースクエリを実行せずに、メモリから結果をロードするだけです。


これはRails 4用ですか?
onebree 2015年

@HunterStevens:はい、そうです
Aaditi Jain

54

パフォーマンスに関する考慮事項に加えて、機能にも違いがあります。コメントに参加するときは、コメントのある投稿(デフォルトでは内部結合)を求めています。コメントを含めると、すべての投稿(外部結合)が必要になります。


10

tl; dr

私はそれらを2つの方法で対比します:

結合 -レコードの条件付き選択用。

含む -結果セットの各メンバーで関連付けを使用する場合。

長いバージョン

結合は、データベースからの結果セットをフィルタリングするためのものです。これを使用して、テーブルのセット操作を実行します。これを集合論を実行するwhere句と考えてください。

Post.joins(:comments)

と同じです

Post.where('id in (select post_id from comments)')

ただし、コメントが複数ある場合は、結合で重複した投稿が返されます。ただし、すべての投稿はコメント付きの投稿になります。これを個別に修正できます:

Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2

規約では、includesメソッドはリレーションを参照するときに追加のデータベースクエリがないことを確認するだけです(n + 1クエリを作成しないようにします)。

Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.

教訓は、joins条件付き集合演算を実行するincludes場合に使用し、コレクションの各メンバーでリレーションを使用する場合に使用します。


それdistinctはいつも私を捕まえます。ありがとうございました!
ベン・ハル

4

.joinsはデータベース結合として機能し、2つ以上のテーブルを結合して、選択したデータをバックエンド(データベース)からフェッチします。

.includesは、データベースの左結合として機能します。左側のすべてのレコードをロードしましたが、右側のモデルの関連性はありません。関連付けられているすべてのオブジェクトをメモリにロードするため、熱心なロードに使用されます。インクルードクエリの結果で関連付けを呼び出すと、データベースでクエリが実行されません。メモリに既にデータが読み込まれているため、メモリからデータが返されます。


0

'joins'はテーブルを結合するためだけに使用され、結合で関連付けを呼び出すと、クエリが再度起動されます(つまり、多くのクエリが起動されます)

lets suppose you have tow model, User and Organisation
User has_many organisations
suppose you have 10 organisation for a user 
@records= User.joins(:organisations).where("organisations.user_id = 1")
QUERY will be 
 select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1

it will return all records of organisation related to user
and @records.map{|u|u.organisation.name}
it run QUERY like 
select * from organisations where organisations.id = x then time(hwo many organisation you have)

この場合、SQLの総数は11です。

しかし、 'includes'を使用すると、インクルードされた関連付けを積極的にロードしてメモリに追加し(最初のロード時にすべての関連付けをロードします)、クエリを再度起動しません

@ records = User.includes(:organisations).where( "organisations.user_id = 1")のようなインクルードを含むレコードを取得すると、クエリは次のようになります。

select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
and 


 select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
and when you run this 

@ records.map {| u | u.organisation.name}クエリは実行されません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.