我有以下假想课
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问题?
答案 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