【问题标题】:Optimize associated queries优化关联查询
【发布时间】:2016-10-15 06:14:48
【问题描述】:

让我们举个例子。我们有两个具有这种关联的模型

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 )

【问题讨论】:

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


    【解决方案1】:

    当您检索单个帖子时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...>
    

    当您检索帖子列表时,您应该解决“n+1 查询问题”。你可以在这里阅读它http://guides.rubyonrails.org/active_record_querying.html(13 Eager Loading Associations)

    您可以使用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 with users 并获取您需要的属性

    @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 来指定您需要的 Post 或 User 的哪些属性。整个帖子将被检索,整个用户将被急切加载。但是,有一种方法可以通过在belongs_to 关联中指定用户的加载属性来限制它们。有一个类似的问题:ActiveRecord includes. Specify included columns

    另外,请记住,Post.joins(:user) 不会返回没有用户的帖子。因此,如果可能并且您需要所有帖子,请改用.left_outer_joins

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

    【讨论】:

    • 在我的示例中,我有此代码 @posts = Post.select("id, user_id, title, slug").includes(:user) .. 所以,如果我在两个答案中都理解正确,为了从 users 中选择特定字段(例如只有 id 和 name),我应该使用joins 而不是includes ??
    • 我不完全理解你的问题。我为我的答案添加了一些额外的解释。如果您不需要每个 Post (post.user) 的 User 实例,但只需要具有属于其用户的某些属性的 Post 实例,请使用 joins。当您不仅需要用户的属性,还需要用户实例以使用来自用户模型的逻辑时,请使用 includes
    【解决方案2】:

    【讨论】:

    • 我对我的查询进行了更新.. 基本上我想在 Rails 4 上使用 selectincludes
    • 你可以像Post.includes(:user).select("posts.id, posts.title, users.name AS user_name")一样使用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-01
    • 2022-01-20
    • 2015-12-01
    相关资源
    最近更新 更多