rails eager load has_many通过连接表

时间:2015-06-23 10:13:07

标签: sql ruby-on-rails has-many-through eager-loading jointable

我的开发环境是在Rails 4.1和postgresql

我有3个带有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]]

有什么建议吗?

3 个答案:

答案 0 :(得分:1)

我希望这是因为你正在使用&#34; count&#34;方法,根据:

http://dev.mensfeld.pl/2014/09/activerecord-count-vs-length-vs-size-and-what-will-happen-if-you-use-it-the-way-you-shouldnt/

&#34;在对象生命周期内没有存储在内部,这意味着,每次调用此方法时,都会再次执行SQL查询&#34;

尝试使用 .length .size 方法。

注意:(引用文章)

length - collection.length

  • 在不执行其他查询的情况下返回集合的长度... 只要集合已加载
  • 当我们有延迟加载的集合时,长度将加载整个集合 进入记忆然后将返回它的长度
  • 当使用不当时,可能会使用所有内存
  • 拥有急切的藏品时真的很快

size - collection.size

  • 结合以前两种方法的能力;
  • 如果加载了集合,将计算它的元素(没有额外的 查询)
  • 如果未加载集合,将执行其他查询

答案 1 :(得分:1)

点击此链接后,我找到了针对我案例的解决方案:Rails Eager Loading and where clause

我不知道Active Record Query Method:选择(http://apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/select

似乎我无法急切加载连接表,因为在包含item_parts之后我在项目部分的迭代过程中使用了where子句。

似乎where子句每次在迭代期间使用时都会对db进行新的查询。

相反,select方法不会每次都访问db,而是处理加载到内存中的Active Record集合。

所以我改变了这个:

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)

答案 2 :(得分:0)

再看一遍,我不确定为什么要将计数添加到连接表中。

@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