0

Ich benutze Rails 5.0.0.1 ATM und ich habe Probleme mit ActiveRecord Beziehungen bei der Optimierung der Anzahl meiner DB Anfragen. Im Moment habe ich: Modell A (sagen wir 'Bestellungen'), Modell B ('OrderDispatches'), Modell C ('Person') und Modell D ('PersonVersion').ActiveRecord benutzerdefinierte has_one Beziehungen

Tabelle 'Menschen' besteht nur aus 'ID' und 'versteckte' Flagge, Rest der Leute Daten sitzt in 'Person_Versionen' ('Name', 'Nachname' und einige Dinge, die sich im Laufe der Zeit ändern können, wie wissenschaftlicher Titel).

Jede Bestellung hat 'receiving_person_id' wie für die Person, die den Auftrag in der DB erfasst hat und jeder OrderDispatch hat 'dispatching_person_id' für die Person, die den Auftrag geliefert hat. Auch Order und OrderDispatch haben Erstellungszeit.

Eine Bestellung hat viele Versendungen.

Die einfachen Beziehungen so ist:

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

Aber wenn ich meine Bestellung mit nach Depeschen Liste muß ich mit N + 1 Situation umgehen, denn genau zu finden (nach dem Erstellungsdatum der Bestellung/OrderDispatch) PersonVersion für jede receiving_person_id und dispatching_person_id Ich mache noch eine Anfrage.

SELECT * 
FROM person_versions 
WHERE effective_date_from <= ? AND person_id = ? 
ORDER BY effective_date_from 
LIMIT 1 

Erstes "?" ist das Erstellungsdatum von Order/OrderDispatch und das zweite '?' empfängt die ID der Person.

Mit dieser Abfrage bekomme ich genaue Personendaten für die Zeit der Bestellung/OrderDispatch Erstellung.

Es ist ziemlich einfach zu schreiben Abfrage mit Unterabfrage (oder Unterabfragen, wie Order mit OrderDispatches auf einer Liste) in Raw SQL, aber ich habe keine Ahnung, wie dies mit ActiveRecord zu tun.

Ich habe versucht, individuelle has_one Beziehung zu schreiben, da dies so weit wie ich bin gekommen ist:

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 

Es funktioniert, wenn ich diese nur verwenden, für die Aufnahme oder die Person Dispatching. Wenn ich versuche, dies für verknüpfte Orders und order_dispatches-Tabellen zu laden, dann muss einer von 'person_versions' Aliasing sein und in meiner benutzerdefinierten where-Klausel ist das nicht möglich (es gibt keine Möglichkeit vorherzusagen, ob es Aliasing wird oder nicht) .

Verschiedener aproach wäre dies:

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 

Raw 'person_versions' in dem in Ordnung ist, weil es in Unterabfrage ist und mit Symbol ': id' macht rohe SQL-Tabelle Aufträge verbunden richtige Aliase für person_versions bekommen und order_dispatches, aber ich bekomme 'IN' anstelle von 'eqauls' für person_versions.id xx subquery und MySQL kann LIMIT nicht in Unterabfragen ausführen, die mit IN/ANY/ALL-Anweisungen verwendet werden, daher bekomme ich zufällige person_version.

Also TL; DR Ich muss 'has_many through' nach 'has_one' transformieren mit benutzerdefinierten 'where' Klausel, die nach neuesten Datensatz unter denen sucht, deren Datum niedriger ist als die ursprüngliche Datensatzerstellung.

EDIT: Eine andere TL; DR zur Vereinfachung

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 

Ich brauche diese Methode umgewandelt 'has_one' Beziehung, so dass ich konnte 'eager_load' diese.

Antwort

1

würde ich das Schema ändern, wie Sie es mit Ihrem Business-Bereich in Konflikt ist, Restrukturierung, es wäre Ihr n + 1 Problem

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

Eigentlich zu lindern, sollte es wie mehr aussehen, wenn ich werde diese, so tun: 'Klasse Order Xenor

+0

Diese Änderung ist eine Überlegung wert, obwohl gerade jetzt, wenn jemand vergisst, mir zu sagen, dass ihr Name oder eine andere Spezifikationen haben sich geändert Ich kann die Version mit effective_start_date in der Vergangenheit erstellen und sie wird alle früheren Bestellungen und Versendungen korrekt beeinflussen, da die Versionssuche dynamisch durchgeführt wird. Mit Ihrem Ansatz sollte ich einige Logik machen, um alle Bestellungen und Order_dispatches zu überprüfen, wenn ich neue Person_Version mit Effective_Start_Date in der Vergangenheit erstellen oder Effective_Start_Date ändern. Es ist ziemlich selten, aber ich muss immer noch mit meinen Kollegen über ein verändertes Schema gehen. Danke für die Antwort! – Xenor

Verwandte Themen