Rails 5.0.0.1 ATM を使用していますが、DB リクエストの数を最適化する際に ActiveRecord 関係の問題に遭遇しました。現在、モデル A ('Orders' としましょう)、モデル B ('OrderDispatches')、モデル C ('Person')、モデル D ('PersonVersion') があります。
テーブル 'people' は 'id' と 'hidden' フラグのみで構成され、残りの人物データは 'person_versions' ('name'、'surname'、および学名など、時間の経過とともに変化するもの) に格納されます。
すべての Order には、DB に注文を記録した人に関する「recoming_person_id」があり、すべての OrderDispatch には、注文を配達した人に関する「dispatching_person_id」があります。また、Order と OrderDispatch には作成時間があります。
1 つの注文で複数の発送があります。
したがって、直接的な関係は次のとおりです。
has_many :receiving_person, through: :person, foreign_key: "receiving_person_id", class_name: 'PersonVersion'
しかし、ディスパッチに応じて注文をリストすると、N + 1 の状況に対処する必要があります。なぜなら、(Order/OrderDispatch の作成日に従って) 正確な PersonVersion をすべての receive_person_id および dispatching_person_id に対して見つけるために、別のリクエストを行っているからです。
SELECT *
FROM person_versions
WHERE effective_date_from <= ? AND person_id = ?
ORDER BY effective_date_from
LIMIT 1
初め '?' Order/OrderDispatch の作成日と 2 番目の「?」受発注者IDです。
このクエリを使用して、Order/OrderDispatch 作成時の正確な個人データを取得しています。
生のSQLでサブクエリ(またはOrderが1つのリストに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」の1つをエイリアスする必要があり、カスタム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
サブクエリにあり、シンボル ':id' を使用すると、生の SQL が注文と order_dispatches に結合された person_versions テーブルの正しいエイリアスを取得するため、生の 'person_versions' は問題ありませんが、person_versions.id の 'equals' ではなく 'IN' を取得します。 xx サブクエリと MySQL は、IN/ANY/ALL ステートメントで使用されるサブクエリで LIMIT を実行できないため、ランダムな person_version を取得します。
したがって、TL;DR 'has_many through' を 'has_one' に変換する必要があります。カスタムの 'where' 句を使用して、元のレコード作成よりも日付が古いものの中から最新のレコードを探します。
編集: 簡略化のための別の 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
これを「eager_load」できるように、このメソッドを「has_one」リレーションに変換する必要があります。