0

私はRails 5.0.0.1 ATMを使用しています。私はDBリクエストの数を最適化するときにActiveRecordリレーションに関する問題を抱えてきました。 モデルA(「受注」と言う)、モデルB(「受注明細」)、モデルCActiveRecordのカスタムhas_oneの関係

テーブル '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の状況に対処する必要があります(注文/ OrderDispatchの作成日に従って)recipient_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の作成日で、2番目の '?'受注/発注者IDです。

このクエリを使用すると、Order/OrderDispatchの作成時に正確な人物データが得られます。

Raw SQLでサブクエリ(またはOrderがOrderDispatchesを1つのリストに持つため、サブクエリを含む)を使用してクエリを書くのはかなり簡単ですが、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 

私は人を受け取るか、ディスパッチするため、こののみを使用する場合、それは動作します。私がeager_loadをJoin OrderとOrder_dispatchesテーブルのためにしようとすると、 'person_versions'の1つにエイリアスを付けなければならず、私のカスタム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テーブルの注文に参加し、正しいエイリアスを取得行い、私はperson_versions.id xxサブクエリのために 'eqauls'の代わりに 'IN'を取得し、MySQLはIN/ANY/ALLステートメントで使用されるサブクエリでLIMITを実行できないため、ランダムなperson_versionを取得します。

だから私は、元のレコード作成よりも新しいレコードの中で最新のレコードを探すカスタム 'where'節を使って 'has_many through'から 'has_one'に変換する必要があります。

EDIT:もう一つのTL;簡素化

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 

のためのDRは、私は、私はこれを 'eager_load' ことができるように、この方法は、 'has_oneの' 関係に変換する必要があります。それはあなたのビジネスドメインと競合だと

答えて

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つの問題を緩和するだろうリストラ、あなたのスキーマを変更します: 'クラス受注 Xenor

+0

は、この変更は、今も、検討する価値がありdispatching_person_version、CLASS_NAME:PersonVersion にhas_one仕様が変わった過去のeffective_start_dateでバージョンを作ることができ、バージョンルックアップが動的に行われるため、すべての過去の注文とディスパッチに正しく影響します。 aproachを使用する私は過去にeffective_start_dateで新しいperson_versionを作成するか、effective_start_dateを変更すると、すべての注文とorder_dispatchesを確認するロジックを作成する必要があります。それはむしろめったに行われませんが、私は同僚とスキーマを変更する必要があります。応答ありがとう! – Xenor

関連する問題