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个人。我知道从性能角度来看,执行联接而不是进行单个查询并不总是总好(实际上,如果按时间花费,看起来上面的两个查询比联接要快),但是毕竟是文档我一直在阅读,我很惊讶地发现它:include不能像广告中所说的那样工作。

也许Rails的认识到性能问题,并除非在某些情况下,不加入呢?


3
如果您使用的是旧版的Rails,请通过标记或在问题正文中进行说明。否则,如果您正在使用Rails 4 NOW,则为includes(对于阅读此文件的任何人)
onebree

现在也有:preload和:eager_loadblog.bigbinary.com/2013/07/01/…–
CJW

Answers:


179

看来该:include功能已在Rails 2.1中更改。Rails曾经在所有情况下都执行过联接,但是出于性能原因,在某些情况下将其更改为使用多个查询。 Fabio Akita撰写的此博客文章提供了有关此更改的一些很好的信息(请参阅标题为“优化的紧急加载”的部分)。



这非常有帮助,谢谢。我希望有一种方法可以强迫Rails进行连接,即使没有“需要”的地方。在某些情况下,您知道联接将更有效,并且不会产生重复的风险。
Jonathan Swartz


@JonathanSwartz看起来新版本的Rails使用eagerload支持此功能。感谢您的链接NathanLong
rubyprince

92

.joins将仅联接表并带来所选字段作为回报。如果您对联接查询结果调用关联,它将再次触发数据库查询

:includes将渴望加载所包含的关联并将其添加到内存中。:includes加载所有包含的表属性。如果您对包含查询结果调用关联,则不会触发任何查询


71

join和include之间的区别在于,使用include语句会生成一个更大的SQL查询,它将其他表中的所有属性加载到内存中。

例如,如果您有一个充满评论的表,并且使用:joins => users来提取所有用户信息以进行排序,等等,那么它将很好用,并且比:include花费的时间更少,但是您想显示要使用:joins获取信息,它将必须为其获取的每个用户分别进行SQL查询,而如果使用:include,则可以使用此信息。

很好的例子:

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


55

我最近在阅读有关Rails :joins:includesRails 之间差异的更多信息。这是我所理解的解释(带有示例:))

考虑这种情况:

  • 一个用户有很多评论,一条评论属于一个用户。

  • 用户模型具有以下属性:名称(字符串),年龄(整数)。Comment模型具有以下属性:Content,user_id。对于注释,user_id可以为null。

加入:

:joins 在两个表之间执行内部联接。从而

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(用户表)的所有记录。因此,如果你这样做

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 在两个表之间执行左外部联接。从而

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">]>

如图所示,它将获取注释.user_id为零的记录。

而且包括将两个表都加载到内存中。因此,如果你这样做

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

如您所见,comment_1.user.age只是从内存中加载结果,而无需在后台触发数据库查询。


这是Rails 4的吗?
6

@HunterStevens:是的
Aaditi Jain

54

除了性能方面的考虑之外,功能上也有所不同。加入评论时,您要求的是带有评论的帖子-默认情况下为内部联接。当您包含评论时,您要求的是所有帖子,即外部联接。


10

tl; dr

我通过两种方式对它们进行对比:

joins-用于条件选择记录。

包括 -在结果集的每个成员上使用关联时。

较长的版本

联接用于过滤来自数据库的结果集。您可以使用它对表进行设置操作。将此视为执行集合论的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用作数据库联接,它联接两个或多个表并从后端(数据库)获取选定的数据。

.includes作为数据库的左连接。它加载了左侧的所有记录,与右侧模型没有任何关系。它用于渴望加载,因为它会将所有关联的对象加载到内存中。如果我们在包含查询结果上调用关联,那么它不会在数据库上触发查询,它只是从内存中返回数据,因为它已经在内存中加载了数据。


0

“联接”仅用于联接表,当您在联接上调用关联时,它将再次触发查询(这意味着将触发许多查询)

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},不会触发任何查询

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.