web-dev-qa-db-ja.com

Rails:includeと:joins

これは、「これをどうやってやるのか」という質問ではなく、「これをどうやってやるのかわからない」という質問ではありません...

したがって、使用することがわかっている関連レコードをプルすることの福音は、: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) 

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

Post.all(:joins => :comments)

そしてログは示します:

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

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

おそらくRails isはパフォーマンスの問題を認識しており、特定の場合を除いて参加しませんか?

333
Rob Cameron

:include機能がRails 2.1で変更されたようです。 Railsはすべての場合で結合を実行していましたが、パフォーマンス上の理由から、状況によっては複数のクエリを使用するように変更されました。 このブログの投稿 Fabio Akitaによる変更に関するいくつかの良い情報があります(「Optimized Eager Loading」というタイトルのセクションを参照)。

171
Greg Campbell

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

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

87
Prem

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

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

素晴らしい例:

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

69
holden

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

52
Brian Maltzan

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

このシナリオを考慮してください:

  • ユーザーはコメントをhas_manyし、コメントはユーザーに属します。

  • 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 []>

示されているように空の配列を取得します。

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

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つのテーブル間でleft outer joinを実行します。かくして

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は、バックグラウンドでデータベースクエリを起動せずに、メモリから結果をロードするだけです。

49
Aaditi Jain

tl; dr

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

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

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

より長いバージョン

結合は、データベースからの結果セットをフィルタリングするためのものです。これを使用して、テーブルの設定操作を行います。これは、集合論を実行する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を使用することです。

9
Kevin Choubacha

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

.includesは、データベースの左結合として機能します。左側のすべてのレコードをロードしましたが、右側のモデルの関連性はありません。メモリ内のすべての関連オブジェクトをロードするため、積極的なロードに使用されます。インクルードクエリ結果でアソシエーションを呼び出すと、データベースでクエリを起動しません。メモリにデータを既にロードしているため、メモリからデータを返すだけです。

4
user3412661

テーブルを結合するために使用される「結合」と、結合でアソシエーションを呼び出すと、クエリが再び起動されます(多くのクエリが起動することを意味します)

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}クエリは起動しません

0
Thorin