【发布时间】:2017-04-22 20:21:14
【问题描述】:
我使用的是 Rails 5.0.0.1 ATM,在优化我的数据库请求计数时遇到了 ActiveRecord 关系问题。 现在我有: A 型(比如说“订单”)、B 型(“OrderDispatches”)、C 型(“Person”)和 D 型(“PersonVersion”)。
表 'people' 仅包含 'id' 和 'hidden' 标志,其余人员数据位于 'person_versions' 中('name'、'surname' 和一些可能随时间变化的东西,如科学标题) .
对于在 DB 中记录订单的人,每个订单都有“receiving_person_id”,每个 OrderDispatch 都有交付订单的人的“dispatching_person_id”。 Order 和 OrderDispatch 也有创建时间。
一个订单有很多分派。
因此直接的关系是:
has_many :receiving_person, through: :person, foreign_key: "receiving_person_id", class_name: 'PersonVersion'
但是当我根据调度列出我的订单时,我必须处理 N+1 情况,因为要为每个接收人员 ID 和调度人员 ID 找到准确的(根据订单/订单调度的创建日期)PersonVersion,我正在发出另一个请求。
SELECT *
FROM person_versions
WHERE effective_date_from <= ? AND person_id = ?
ORDER BY effective_date_from
LIMIT 1
第一个'?'是 Order/OrderDispatch 创建日期和第二个“?”正在接收/订购人员 ID。
使用此查询,我可以获得创建 Order/OrderDispatch 时的准确人员数据。
在原始 SQL 中使用子查询(或子查询,因为 Order 在一个列表中带有 OrderDispatches)编写查询相当容易,但我不知道如何使用 ActiveRecord 来做到这一点。
我尝试编写自定义的 has_one 关系,因为这是我来的:
has_one :receiving_person. -> {
where("person_versions.id = (
SELECT id
FROM person_versions sub_pv1
WHERE sub_pv1.date_from <= orders.receive_date
AND sub_pv1.person_id = orders.receiving_person_id
LIMIT 1)")},
through: :person, class_name: "PersonVersion", primary_key: "person_id", source: :person_version
如果我仅将其用于接收或调度人员,则它可以工作。当我尝试为加入的订单和 order_dispatches 表进行 eager_load 时,必须为“person_versions”之一加上别名,而在我的自定义 where 子句中它不是(无法预测它是否会被别名,它被用于两种方式) .
不同的方法是这样的:
has_one :receiving_person, -> {
where(:id => PersonVersion.where("
person_versions.date_from <= orders.receive_date
AND person_versions.person_id = orders.receiving_person_id").order(date_from: :desc).limit(1)},
through: :person, class_name: "PersonVersion", primary_key: "person_id", source: :person_version
原始 'person_versions' 在 where 是可以的,因为它在子查询中并且使用符号 ':id' 使原始 SQL 获得连接到订单和 order_dispatches 的 person_versions 表的正确别名,但我得到的是 'IN' 而不是 'eqauls' person_versions.id xx 子查询和 MySQL 不能在与 IN/ANY/ALL 语句一起使用的子查询中做 LIMIT,所以我只得到随机的 person_version。
所以 TL;DR 我需要使用自定义“where”子句将“has_many through”转换为“has_one”,该子句在日期低于原始记录创建日期的记录中查找最新记录。
编辑:另一个 TL;简化的 DR
def receiving_person
receiving_person_id = self.receiving_person_id
receive_date = self.receive_date
PersonVersion.where(:person_id => receiving_person_id, :hidden => 0).where.has{date_from <= receive_date}.order(date_from: :desc, id: :desc).first
end
我需要将此方法转换为“has_one”关系,以便我可以“eager_load”这个。
【问题讨论】:
标签: ruby-on-rails activerecord subquery has-many has-one-through