【问题标题】:Rails 5 Eager load and then find_byRails 5 Eager load 然后 find_by
【发布时间】:2018-10-11 14:00:50
【问题描述】:

我有如下三个模型:

class Parent < ApplicationRecord
  has_many :children
  has_many :assets
end

class Child < ApplicationRecord
  belongs_to :parent
end

class Asset < ApplicationRecord
  belongs_to :parent
end

现在我需要通过父母找出属于孩子的资产。并且“资产”具有资产类型列。所以我需要做这样的事情

Parent.first.children.each do |child|
  child.parent.assets.find_by(asset_type: "first").asset_value
end

如何避免 N+1 查询?

导轨:5.1.6

红宝石:2.3.4

【问题讨论】:

  • 我认为您在该查询中没有 N+1 问题,除非您想做类似Child.each do |child|
  • 是的,你是对的。我需要迭代孩子。对不起,我错过了。编辑了帖子。谢谢
  • 也许这只是个例子,但我不明白你为什么说Parent.first.children,然后是child.parent.assets。既然孩子只有一个父母,那么你应该说Parent.first.assets.find_by(asset_type: 'first').asset_value
  • @KPheasey 我猜这是一个玩具示例,这个想法需要应用于parents 的集合,并且每个子资产需要做的事情比显示的要多这里。很好地避免了 IMO 的噪音。
  • 是的,这只是一个例子。真实场景更复杂。孩子患有性病。

标签: ruby-on-rails activerecord ruby-on-rails-5 eager-loading


【解决方案1】:

第一个问题是添加find_by总是执行另一个查询,无论您预加载了什么(至少从 Rails 4 开始,我怀疑它已经改变了)。这是因为实现find_by 是为了生成更多的SQL。如果你想预加载,你可以改用find,只要每个父级的资产数量不高就很好,但如果有很多很多资产和/或它们是大型对象,那就不好了会占用大量内存(请参阅下面的注释了解 alt 解决方案)。

您可以像这样预加载资产:

parent.children.preload(:parent => :assets) each do |child|
# Will not execute another query
child.parent.assets.find{ |asset| asset.asset_type == "first" }

或者,您可以声明has_many :through 关联:

class Child < ActiveRecord::Base
  belongs_to :parent
  has_many :assets, through: :parent
  ...
end

那你就可以了

parent.children.preload(:assets).each do |child|
# Will not execute another query
child.assets.find { |asset| asset.asset_type == "first" }

如果你想在 db 层而不是 ruby​​ 中执行 find,你可以定义一个作用域关联:

class Parent < ActiveRecord::Base
  has_one :first_asset, ->{ where asset_type: "first" }
  ...
end

这样你就可以preload(:parent =&gt; :first_asset)了。

【讨论】:

  • 你是英雄。这个想法帮助我最小化了查询时间。谢谢
猜你喜欢
  • 1970-01-01
  • 2011-05-29
  • 1970-01-01
  • 2017-02-22
  • 1970-01-01
  • 1970-01-01
  • 2020-06-10
  • 1970-01-01
  • 2013-06-20
相关资源
最近更新 更多