问题描述
我有以下三种模型:
class Parent < ApplicationRecord
has_many :children
has_many :assets
end
class Child < ApplicationRecord
belongs_to :parent
end
class Asset < ApplicationRecord
belongs_to :parent
end
现在我需要找出资产通过父母属于孩子的孩子。并且资产具有asset_type列。因此,我需要执行以下操作
Now I need to find out the assets that belong to a child through parent. And "Asset" has asset_type column. So I need to do something like this
Parent.first.children.each do |child|
child.parent.assets.find_by(asset_type: "first").asset_value
end
如何避免N + 1个查询?
How can I do this avoiding N+1 queries?
路轨:5.1.6
红宝石:2.3.4
推荐答案
第一个问题是添加 find_by
无论您预加载了什么,它都会始终执行另一个查询(至少从Rails 4开始,我怀疑它已经改变了)。这是因为实现了 find_by
可以生成更多的SQL。如果要预加载,则可以使用 find
来代替,只要每个父级的资产数量都不是可笑的,这很好,但是如果有很多资产,则不好很多资产和/或它们是会占用大量内存的大对象(请参阅下面的alt解决方案注释)。
First issue is that adding a find_by
will always execute another query no matter what you have preloaded (at least as of Rails 4, I doubt it has changed though). This is because find_by
is implemented to generate more SQL. If you want to preload, you can use find
instead, which is fine as long as there aren't a ridiculous number of assets per parent, but bad if there are lots and lots of assets and/or they are large objects that would take up a ton of memory (see note below for alt solution).
您可以像这样预加载资产:
You can preload assets like this:
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
关联:
Alternatively, you can declare a has_many :through
association:
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中执行查找,则可以定义范围关联:
If you want to execute the find in the db layer and not in ruby, you can define a scoped association:
class Parent < ActiveRecord::Base
has_one :first_asset, ->{ where asset_type: "first" }
...
end
通过这种方式,您可以 preload(:parent =>:first_asset)
。
This way you can preload(:parent => :first_asset)
instead.
这篇关于Rails 5急于加载,然后find_by的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!