0

저는 Rails 5.0.0.1 ATM을 사용하고 있습니다. DB 요청 횟수를 최적화 할 때 ActiveRecord 관계에 문제가 있습니다. 모델 A ('Order'), 모델 B ('OrderDispatches'), 모델 C ('Person') 및 모델 D ('PersonVersion').ActiveRecord custom has_one relations

테이블 'people'은 'id'및 'hidden'플래그로만 구성되며, 나머지 사람 데이터는 'person_versions'('name', 'surname'및 시간 경과에 따라 변경 될 수있는 일부 항목, 과학 제목).

모든 주문서는 DB에 주문서를 기록한 사람에게 'receiving_person_id'가 있으며 모든 주문서 발송에는 주문한 'dispatching_person_id'가 있습니다. 또한 Order와 OrderDispatch에는 생성 시간이 있습니다.

하나의 주문에는 여러 개의 발송이 있습니다.

has_many :receiving_person, through: :person, foreign_key: "receiving_person_id", class_name: 'PersonVersion' 

하지만 따라 파견 내 순서를 나열 할 때 정확한 (주문/OrderDispatch의 생성 날짜에 따라 찾을 수 있기 때문에, N + 1 개 상황을 처리해야 :

똑 바른 관계에 따라서입니다) 모든 receiving_person_id 및 dispatching_person_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 :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 

의미가 있습니다 :

나는이 지금까지의 내가 왔어요로 같이 정의 has_one 관계를 작성했습니다. 내가 결합 된 주문 및 order_dispatches 테이블에 대한 다음 'person_versions'중 하나를 별칭을해야 eager_load하려고 할 때 내 사용자 정의 where 절 (그것은 앨리어스 여부를 예측할 수있는 방법은 아닙니다, 그것은 두 가지 방법으로 사용되는) .

다른 aproach이 될 것이다 :

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 

가 하위 쿼리 및 기호를 사용하여 있기 때문에, OK입니다 원시 'person_versions' 'ID'원시 SQL은 person_versions 테이블의 주문에 가입 올바른 별칭을 얻을하게하고 order_dispatches하지만 person_versions.id xx 하위 쿼리의 'eqauls'대신 'IN'이 표시되고 IN/ANY/ALL 문과 함께 사용되는 하위 쿼리에서는 MySQL이 LIMIT를 수행 할 수 없으므로 임의의 person_version을 얻을 수 있습니다.

그래서 TL : 원래 레코드 생성보다 낮은 날짜 중 최신 레코드를 찾는 사용자 정의 'where'절을 사용하여 'has_many through'를 'has_one'으로 변환해야합니다.

EDIT : 다른 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'이 수 있도록.그것은 귀하의 비즈니스 도메인과 충돌 것 같은

답변

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 
+0

실제로 N + 1 문제를 완화 할 구조 조정, 스키마를 변경할 것입니다 : '클래스 주문 <액티브 :: 자료 has_many : order_dispatches, 의존 : receiving_person_version, class_name을 : PersonVersion has_one : receiving_person 통해 : receiving_person_version 끝 클래스 OrderDispatch <법 belongs_to를 파괴 iveRecord :: 자료 belongs_to : 순서 belongs_to : dispatching_person_version, class_name을 : PersonVersion has_one : dispatching_person 통해 : receiving_person_version end' – Xenor

+0

지금 누군가가 저에게 잊어 버린 경우에도이 변화는 고려 가치가 자신의 이름 또는 다른 스펙이 변경되었습니다. 과거의 effective_start_date로 버전을 만들 수 있으며, 버전 조회가 동적으로 완료되기 때문에 모든 과거 주문 및 디스패치에 올바르게 영향을 미칩니다. aproach를 사용하여 과거에 effective_start_date와 함께 새 person_version을 만들거나 effective_start_date를 변경할 때 모든 주문과 order_dispatches를 검토하는 로직을 만들어야합니다. 오히려 거의 수행되지는 않지만 여전히 동료와 스키마를 변경해야합니다. 응답 해 주셔서 감사합니다! – Xenor