321

これは「どうすればうまくいくのか」という質問ではなく、「どうしたらよいのか」という質問です。

だから、あなたが使うつもりであることがわかっている関連レコードを引っ張ることの福音は使うことです: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

私は何かが足りないのですか?私は半ダースの関連付けを持つアプリを持っていて、1つの画面に私はそれらすべてのデータを表示しています。 6人の個人の代わりに1つの結合された質問を持つことがより良いように思えます。パフォーマンス的には、個々のクエリよりも結合を実行するほうが常に良いとは限らないことを知っています(実際に時間をかけている場合、上記の2つの個々のクエリーは結合より速いように見えます)。読んでいて見て驚いた:include宣伝どおりに動作しません。

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


  • 古いバージョンのRailsを使用していた場合は、タグまたは質問本体にその旨を記載してください。さもなければ、あなたが今Rails 4を使っているなら、それはincludes(これを読んでいる人のために) - onebree
  • 今もあります:preloadと:eager_loadblog.bigbinary.com/2013/07/01/… - CJW

8 답변


167

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



81

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

:includes含まれている関連付けを積極的に読み込み、それらをメモリに追加します。:includes含まれているすべてのテーブル属性をロードします。 include query resultでアソシエーションを呼び出しても、クエリは起動されません。

俺のブログ記事違いのいくつかの詳細な説明があります


  • リンクが切れているようです。 - Eliad

68

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

たとえば、コメントがいっぱいのテーブルがあり、ソート目的ですべてのユーザー情報を取得するために:joins => usersを使用すると、それはうまく機能し、:includeよりも時間がかかりませんが、表示したいとします。 :joinを使用して情報を取得するには、取得する各ユーザーに対して別々のSQLクエリを作成する必要があります。一方、:includeを使用した場合は、この情報を使用する準備が整います。

良い例:

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


50

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


47

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

このシナリオを考えます:

  • ユーザは多くのコメントを持ち、コメントはユーザに属します。

  • Userモデルには、Name(文字列)、Age(整数)の属性があります。 Commentモデルには、Content、user_idの属性があります。コメントの場合、user_idはnullでもかまいません。

参加する

:joinが実行します内部結合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">]>

取得します(commentsテーブルの)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結果を取得するためにバックグラウンドでデータベースクエリを再度起動します。

含まれています:

:を含む左外部結合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">]>

になりますコメントテーブルの全レコードを含む結合テーブル。あなたがそうすれば

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であるレコードを取得します。

さらに、includesは両方のテーブルをメモリにロードします。あなたがそうすれば

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

お気づきのとおり、comment_1.user.ageは、バックグラウンドでデータベースクエリを起動せずにメモリから結果を単純にロードします。


  • これはRails 4用ですか? - onebree
  • @HunterStevens:はい、そうです - Aaditi Jain

6

tl; dr

私は二つの点でそれらを対比させる:

参加する - レコードの条件付き選択用。

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

長いバージョン

結合は、データベースから取得した結果セットをフィルタリングするためのものです。あなたはあなたのテーブルに集合演算をするためにそれを使います。これを集合論を実行するwhere節と考えてください。

Post.joins(:comments)

と同じです

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

1つ以上のコメントがあるならば、あなたは結合で戻って重複したポストを得るであろうことを除いて。しかし、すべての投稿はコメントがある投稿になります。あなたはこれをdistinctで修正できます。

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

契約では、includesmethodは、関係を参照するときに追加のデータベースクエリがないことを単に確認します(したがって、n + 1クエリを作成しないようにします)。

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

道徳的です、使用joins条件付き集合演算を実行したいときincludesコレクションの各メンバーでリレーションを使用しようとしているとき。


  • それdistinct毎回私をつかまえます。ありがとうございました! - Beejamin

4

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

データベースの左結合としての作業を含みます。左側の全レコードをロードしており、右側モデルの関連性はありません。メモリ内のすべての関連オブジェクトをロードするため、ロードを積極的に行うために使用されます。インクルードクエリ結果に関連付けを呼び出した場合、データベースにクエリは実行されません。既にメモリにデータがロードされているため、単純にメモリからデータが返されます。


0

'join'はテーブルを結合するために使われていましたが、結合で結合を呼んだときには再びクエリを起動します(多くのクエリが起動することを意味します)。

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(:organization).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}     クエリは発生しません

リンクされた質問


関連する質問

最近の質問