Rails 3 中的左外连接

87

我有以下代码:

@posts = Post.joins(:user).joins(:blog).select

这个查询旨在查找所有的帖子并返回关联用户和博客。 然而,用户是可选的,这意味着:joins生成的INNER JOIN没有返回很多记录。

我该如何将其改为生成LEFT OUTER JOIN


请参阅Rails 4中的LEFT OUTER JOIN - Yarin
8个回答

114
@posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").
              joins(:blog).select

3
如果您只想要没有用户的帖子,该怎么办? - mcr
24
@mcr @posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").joins(:blog).where("users.id IS NULL").select - Linus Oleander
1
select 需要参数吗?这不应该是 select('posts.*') 吗? - Kevin Sylvestre
在Rails 3中,这是您拥有真正控制联接并确切知道正在发生什么的唯一方法。 - Joshua Pinter

75

你可以使用includes来实现这个,具体请查看Rails指南上的文档:

Post.includes(:comments).where(comments: {visible: true})

结果为:

SELECT "posts"."id" AS t0_r0, ...
       "comments"."updated_at" AS t1_r5
FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
WHERE (comments.visible = 1)

14
根据我的测试,“includes”并不执行联接操作,而是执行单独的查询来获取关联。因此,它避免了N+1问题,但与使用JOIN一次性获取记录的方式不同。 - Kris
7
@Kris,你说的有一定道理。这是一个需要注意的问题,因为includes函数会根据使用情境有两种不同的作用。如果你阅读第12节的全部内容,Rails指南将比我更好地解释它:http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations - WuTangTan
4
如果你不需要WHERE字句,includes将生成两个查询而不是一个JOIN,这只回答了问题的一部分。 - Rodrigue
14
这会在Rails 4中生成警告,除非您还添加了references(:comments)。此外,由于includes,这将导致所有返回的评论都被急切地加载到内存中,这可能不是您想要的。 - Derek Prior
如果您将此代码放入作用域中,并且出于某些原因稍后添加了 .joins(:comments),则 LEFT OUTER JOIN 将被转换为 INNER JOIN... - gtournie
2
为了让这更加“Railsy”:Post.includes(:comments).where(comments: {visible: true})。这样你也不需要使用 references - michael

11
我是squeel gem的忠实粉丝:
Post.joins{user.outer}.joins{blog}

它支持内连接和外连接,还可以指定多态 belongs_to 关系的类/类型。

10

使用eager_load

@posts = Post.eager_load(:user)

8
默认情况下,当您传递一个命名关联给ActiveRecord::Base#joins时,它将执行一个INNER JOIN。您需要传递一个表示LEFT OUTER JOIN的字符串。
文档中可以看到:

:joins - 要么是用于额外连接的SQL片段,例如"LEFT JOIN comments ON comments.post_id = id"(很少需要),要么是与:include选项使用相同形式的命名关联,这将在相关表上执行INNER JOIN,或者是包含字符串和命名关联混合的数组。

如果值是字符串,则记录将以只读方式返回,因为它们具有不对应于表列的属性。传递:readonly => false以覆盖此行为。


7

在ActiveRecord中有一个left_outer_joins方法。您可以像这样使用它:

@posts = Post.left_outer_joins(:user).joins(:blog).select

1
这在Rails 3中似乎不存在,这正是发帖者所询问的。 - cesoid
正确;这是在Rails 5.0.0中引入的。 - Ollie Bennett

4

好消息,Rails 5现在支持 LEFT OUTER JOIN。你的查询语句现在应该是这样的:

@posts = Post.left_outer_joins(:user, :blog)

0
class User < ActiveRecord::Base
     has_many :friends, :foreign_key=>"u_from",:class_name=>"Friend"
end

class Friend < ActiveRecord::Base
     belongs_to :user
end


friends = user.friends.where(:u_req_status=>2).joins("LEFT OUTER JOIN users ON users.u_id = friends.u_to").select("friend_id,u_from,u_to,u_first_name,u_last_name,u_email,u_fbid,u_twtid,u_picture_url,u_quote")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接