1

A:parent has_many :childrenと私は、親の最年長の子供の年齢を の属性として取得しようとしていますparent。私は、それを効率的に達成するあらゆるソリューションを受け入れます。

サブクエリを実行しようとしている理由は、n+1親ごとに個別の DB 要求を行うのではなく、DB にオーバーヘッドを任せるためです。どちらも非効率的ですが、サブクエリを使用する方が効率的です。

# attributes: id
class Parent < ActiveRecord::Base
  has_many :children

  # Results in an (n+1) request
  def age_of_oldest_child
    children.maximum(:age)
  end
end

# attributes: id, parent_id, age
class Child < ActiveRecord::Base
  belongs_to :parent
end

ユースケースの例:

parent = Parent.first.age_of_oldest_child # => 16

parents = Parent.all
parents.each do |parent|
  puts parent.age_of_oldest_child # => 16, ...
end

私の試み:

sql = "
  SELECT 
    (SELECT
      MAX(children.age)
      FROM children
      WHERE children.parent_id = parents.id
    ) AS age_of_oldest_child
  FROM
    parents;
"

Parent.find_by_sql(sql)

これは、すべての親の最大年齢の配列を返します。これを 1 つの親だけに制限するか、すべての親を取得するときに親の属性として含めるようにしたいと考えています。

更新 2015-06-19 11:00

これが私が思いついた実行可能な解決策です。より効率的な代替手段はありますか?

class Parent < ActiveRecord::Base
  scope :with_oldest_child, -> { includes(:oldest_child) }

  has_many :children
  has_one :oldest_child, -> { order(age: :desc).select(:age, :parent_id) }, class_name: Child

  def age_of_oldest_child
    oldest_child && oldest_child.age
  end
end

使用例:

# 2 DB queries, 1 for parent and 1 for oldest_child
parent = Parent.with_oldest_child.find(1)

# No further DB queries
parent.age_of_oldest_child # => 16
4

1 に答える 1

0

これを行うには、次の 2 つの方法があります。

親.rb

class Parent < ActiveRecord::Base
  has_many :children

  # Leaves choice of hitting DB up to Rails
  def age_of_oldest_child_1
    children.max_by(&:age)
  end

  # Always hits DB, but avoids instantiating Child objects
  def age_of_oldest_child_2
    Child.where(parent: self).maximum(:age)
  end
end

最初のメソッドは、列挙可能なモジュールの機能を使用し、コレクション内の各オブジェクトmax_byを呼び出します。ageこの方法の利点は、データベースにアクセスするかどうかのロジックを Rails に任せることです。何らかの理由ですでにインスタンス化されている場合はchildren、データベースに再度アクセスすることはありません。ageそれらがインスタンス化されていない場合、選択クエリを実行し、それらを単一のクエリでメモリにロードし (したがって、N+1 を回避)、そのメソッドを呼び出してそれぞれを処理します。

ただし、子がインスタンス化されてから基礎となるデータが変更された場合、古い結果が引き続き使用されるという 2 つの欠点があります (これは、 を:true呼び出すときに渡すことで回避できます:children。また、最初にすべてのシングルchildをメモリにロードしてからカウントします) 。オブジェクトが大きい場合、childおよび/または親に多数の子がある場合、メモリを大量に消費する可能性があります。

これらすべてをロードすることを避けたい場合は、方法 2 で示したクエリchildrenを使用して、毎回直接 DB ヒットを行うことができます。ターゲット モデルの外部でそのようなクエリを実行するためのアンチ パターンですが、これにより例が見やすくなります。countChild

于 2015-06-18T17:37:03.893 に答える