【问题标题】:ActiveRecord custom has_one relationsActiveRecord 自定义 has_one 关系
【发布时间】: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


    【解决方案1】:

    我会更改您的架构,因为它与您的业务域冲突,重组它会缓解您的 n+1 问题

    class Person < ActiveRecord::Base
      has_many :versions, class_name: PersonVersion, dependent: :destroy
      has_one :current_version, class_name: PersonVersion
    end
    
    class PersonVersion < ActiveRecord::Base
      belongs_to :person, inverse_of: :versions, 
    
      default_scope ->{
        order("person_versions.id desc")
      }
    end
    
    class Order < ActiveRecord::Base
      has_many :order_dispatches, dependent: :destroy
    end
    
    class OrderDispatch < ActiveRecord::Base
      belongs_to :order
      belongs_to :receiving_person_version, class_name: PersonVersion
      has_one :receiving_person, through: :receiving_person_version
    end
    

    【讨论】:

    • 实际上,如果我要这样做,它应该看起来更像:class Order &lt; ActiveRecord::Base has_many :order_dispatches, dependent: :destroy belongs_to :receiving_person_version, class_name: PersonVersion has_one :receiving_person, through: :receiving_person_version end class OrderDispatch &lt; ActiveRecord::Base belongs_to :order belongs_to :dispatching_person_version, class_name: PersonVersion has_one :dispatching_person, through: :receiving_person_version end
    • 这个改变值得考虑,虽然现在如果有人忘记告诉我他们的名字或其他一些规格已经改变,我可以在过去制作带有 Effective_start_date 的版本,它会正确影响所有过去的订单和分派,因为版本查找是动态完成的。使用您的方法,当我在过去使用有效开始日期创建新人员版本或更改有效开始日期时,我应该制定一些逻辑来审查所有订单和订单调度。它很少完成,但我仍然必须与我的同事一起讨论更改模式。感谢您的回复!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 2017-11-12
    相关资源
    最近更新 更多