我的开发环境是在Rails 4.1和postgresql 上
我有三个模型与has_many直通关系:
class Item < ActiveRecord::Base
has_many :item_parts
has_many :parts, through: :item_parts
end
class Part < ActiveRecord::Base
has_many :item_parts
has_many :items, through: :item_parts
end
class ItemPart < ActiveRecord::Base
belongs_to :item
belongs_to :part
end
item_parts联接表有一个unit_count属性,用于跟踪项包含的部分数。
当我在视图中遍历特定项的所有部分时,我还需要从联接表中获得unit_count值。
这给了我n+1查询问题。
我试着急切地加载联接表:
item.parts.includes(:item_parts)
ItemPart Load (0.5ms) SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)
但当我这样做之后:
part.item_parts.where(item_id: item.id).first.unit_count
我得到了以下每个部分的sql查询;急切的装载没有起作用
ItemPart Load (0.5ms) SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" = $1 AND "item_parts"."item_id" = $2 [["part_id", 24], ["item_id", 18]]
有什么建议吗?
在这个链接之后,我找到了我的案例的解决方案:Rails Eager Loading和where子句
我不知道活动记录查询方法:选择(http://apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/select)
我似乎无法急于加载联接表,因为在包含item_parts之后,我在项部分的迭代过程中使用where子句。
似乎where子句每次在迭代过程中使用时都会对数据库进行新的查询。
相反,select方法不会每次都命中数据库,而是对加载到内存中的活动记录集合有效。
所以我改了这个:
item.parts.includes(:item_parts)
part.item_parts.where(item_id: item.id).first.unit_count
这个:
item.parts.includes(:item_parts)
part.item_parts.select{|item_part| item_part.item_id == item.id}.first.unit_count
然后我有一个单独的查询来加载item_parts:
ItemPart Load (0.5ms) SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)
我想这是因为您使用的是;计数";方法,根据:
http://dev.mensfeld.pl/2014/09/activerecord-count-vs-length-vs-size-and-what-will-happen-if-you-use-it-the-way-you-shouldnt/
"在对象生命周期期间没有存储在内部,这意味着每次我们调用此方法时,都会再次执行SQL查询;
请尝试使用.length或.size方法。
注:(引用文章)
length–collection.length
- 返回集合的长度,而不执行其他查询…只要集合已加载
- 当我们有懒惰加载的集合时,长度将加载整个集合进入内存,然后返回其长度
- 如果使用不当,可能会占用你所有的内存
- 当拥有一个急切加载的收藏时,速度真的很快
size–collection.size
- 结合了前面两种方法的能力
- 如果集合已加载,将对其元素进行计数(无其他查询)
- 如果未加载集合,将执行附加查询
再看一遍,我不知道为什么要将计数添加到联接表中。
@items = Item.includes(:parts)
@items.each do |item|
parts_count_per_item = item.parts.size
end
或
@parts = Part.includes(:items)
@parts.each do |part|
items_count_per_part = part.items.size
end