Rails避免N + 1 has_one关联

时间:2019-03-11 15:40:52

标签: ruby-on-rails postgresql

我有以下假想课

class A < ActiveRecord::Base
  has_many :objects, -> { order(:created_at) }, class_name: 'B'
  has_one :last_object, -> { order(created_at: :desc).limit(1) }, class_name: 'B'

  scope :with_last_object, -> { includes(:last_object) }
end

我在模型A和范围中添加了第二个关联,以避免在下一种情况下A.all.map(&:last_object)提出N + 1请求。所以我写了A.all.with_last_object.map(&:last_object)。 但是它失败了:它只为A的所有实例检索1 last_object。从Postgres日志中

 SELECT  "b".* FROM "b" WHERE "b"."a_id" IN (1, 2, 3, ...) ORDER BY "b"."created_at" DESC LIMIT $1

在这种情况下是否有办法避免N + 1问题?

2 个答案:

答案 0 :(得分:1)

问题出在您对last_object的定义上,该定义不能将活动记录转换为急切的负载。

要么您都努力地使用自联接,所以您可能会遇到一些棘手的(并且可能表现不佳的)SQL,例如

SELECT  "b".* FROM "b"
JOIN "b" AS "b_from_same_a"
  ON "b"."a_id" = "b_from_same_a"."id"
WHERE "b"."created_at" = MAX("b_from_same_a"."created_at")
GROUP BY "b".*

或者您可以明确跟踪最后一个:

class B < ActiveRecord::Base
  after_save :set_last, on: :create

  def set_last
    self.class.where(a_id: a_id).update_all(last: false)
    self.update_column(last: true)
  end
end

并相应地修改关联范围。

答案 1 :(得分:0)

我就此结束了(感谢我的同事)

has_one :last_object, -> {
    from(
      <<~SQL
        (select * from
          (select
              *,
              row_number() over(partition by b.a_id order by b.created_at desc) as rn
          from
              b) as inner_b
        where rn = 1) as b
      SQL
    )
  }, class_name: 'B', dependent: :delete