Rails:如何在不进行N + 1查询的情况下渴望加载有序关联的有限记录

thi*_*ign 5 ruby database activerecord ruby-on-rails

我知道关于这些主题的问题很多,但是我没有找到涵盖所有方面的问题。

考虑UserActivityLike模型。当我查询一个活动时,我希望为集合中的每个活动加载第一个“赞”,而不会进行N + 1个查询,也不会加载过多的必要记录。我的代码如下所示:

class User < ActiveRecord::Base
  has_many :likes, as: :liker
end

class Activity < ActiveRecord::Base
  has_many :likes
  has_one :first_like, -> { order(created_at: :asc) }, class_name: "Like"
end

class Like < ActiveRecord::Base
  belongs_to :activity
  belongs_to :liker, polymorphic: true
end
Run Code Online (Sandbox Code Playgroud)

我做了一个综合的要点来测试不同的加载策略和方法:https : //gist.github.com/thisismydesign/b08ba0ee3c1862ef87effe0e25386267

策略:N + 1个查询,左外部联接,单个额外查询

方法:eager_loadincludesincludes & referencesincludes & preload(这将导致主要是左外连接或单额外的查询)

这是我发现的问题:

  • 左外部order(created_at: :asc)联接不尊重关联范围default_scope { order(created_at: :asc) }(请参阅:rails issue)。它确实遵守显式排序,即.order("likes.created_at asc")
  • 尽管如此,仍应避免使用左外部联接,因为它“可能导致许多行包含冗余数据,并且在规模上表现不佳”(请参阅​​:apidoc rubydocrails api)。即使在双方都进行索引搜索的情况下,这也是一个存在大量数据的实际问题。
  • 单个额外的查询将创建一个没有限制的查询,可能会获取大量数据(请参阅:rails issue
  • limit(1)关联添加显式内容以希望限制单个额外的查询将破坏事情

首选方法是仅查询所需记录的单个额外查询。总而言之,我找不到Rails的本地解决方案。有没有?

thi*_*ign 1

在我的问题中,我正在寻找一种使用 Rails 的本地方式。不过,这里有一个使用 SQL 和虚拟属性的解决方案:

class Activity < ApplicationRecord
  has_one :first_like, class_name: "Like", primary_key: :first_like_id, foreign_key: :id

  scope :with_first_like, lambda {
    select(
      "activities.*,
      (
        SELECT like_id as first_like_id
        FROM likes
        WHERE activity_id = activities.id
        ORDER BY created_at ASC
        LIMIT 1
      )"
    )
  }
end

Activity.with_first_like.includes(:first_like)
Run Code Online (Sandbox Code Playgroud)