优化关联查询

时间:2016-10-14 08:33:34

标签: mysql sql ruby-on-rails optimization query-optimization

让我们看看下面的例子。我们有两个与此关联的模型

class Post < ActiveRecord::Base
  belongs_to :user  
end

class User < ActiveRecord::Base
  has_many :posts
end

如您所知,我们可以执行以下操作:

<%= @post.title %>
<%= @post.user.name %>
etc etc

此关联通常会生成以下查询:

SELECT * FROM `posts` WHERE `post`.`id` = 1;
SELECT * FROM `users` WHERE `user`.`id` = 15; # from @post.user

现在,如果我想选择某些特定字段(假设暂时没有关联)列出所有帖子或只显示一个帖子,我会这样做:

@posts = Post.select("id, title, slug, created_at").all()
# or
@post = Post.select("id, title, slug, created_at").find(1)

如果关联,如何为关联查询选择特定字段?换句话说,而不是

SELECT * FROM `users`
SELECT * FROM `posts` WHERE `user_id` IN ( user IDs here )

拥有

SELECT `id`, `name` FROM `users`
SELECT `id`, `user_id`, `title`, `slug` FROM `posts` WHERE `user_id` IN ( user IDs here )

2 个答案:

答案 0 :(得分:0)

答案 1 :(得分:0)

select子句中检索限制其属性的单个帖子时,其行为与任何其他帖子相同,但为了能够访问用户,它应具有已选择user_id属性。

@post = Post.select("id").first
# this post doesn't know about it's user since it has no user_id attribute
@post.user # => nil

@post = Post.select("id", "user_id").first
# this post has a user
@post.user # => #<User...>

当您检索帖子列表时,您应该解决&#39; n + 1查询问题&#39;。你可以在这里阅读http://guides.rubyonrails.org/active_record_querying.html(13个预先加载的协会)

您可以将includes用于热切加载关联:

@posts = Post.includes(:user)

@posts.each do |post|
  post.user # this will not make an extra DB query since users are already eager loaded
end

或者,如果您不需要实例化用户并希望获得特定属性,那么更好的解决方案是join与用户并获取您需要的属性

@posts = Post.joins(:user).select("posts.id", "posts.title", "users.name AS user_name")

@posts.each do |post|
  post.user_name # each post now has `user_name` attribute
end

<强>被修改

据我所知,使用includes会忽略select。您无法在此处使用select来指定您需要的帖子或用户的哪些属性。将检索整个帖子并且整个用户将被急切加载。但是,有一种方法可以通过在belongs_to关联中指定用户的加载属性来限制用户的加载属性。对此有一个类似的问题:ActiveRecord includes. Specify included columns

另外,请注意,Post.joins(:user)不会返回没有用户的帖子。因此,如果可能,您需要使用.left_outer_joins来代替所有帖子。

# works in Rails 5
Post.left_outer_joins(:user).select(...)