【问题标题】:Unexpected behavior with ActiveRecord includesActiveRecord 的意外行为包括
【发布时间】:2016-05-23 21:07:46
【问题描述】:

我正在使用 AR includes 方法在对象 UserBuilding 之间执行 LEFT OUTER JOIN,其中用户可能有也可能没有 Building 关联:

users = User.includes(:building).references(:buildings)

由于我使用的是references,任何关联的建筑对象都将被预先加载。

我的期望是,然后我能够遍历用户列表,并检查用户是否有与之关联的建筑物而不触发其他查询,但事实上,每当我尝试访问建筑物属性时,我都会看到这一点对于没有的用户,AR 会进行另一个 SQL 调用以尝试检索该建筑物(尽管在随后的尝试中它只会返回 nil)。

这些查询显然是多余的,因为关联将在初始连接期间加载,并且似乎破坏了使用包含/引用进行急切加载的整个目的,因为现在我正在查看 N 倍的查询数量等于空关联的数量。

users.each do | user |

  # This will trigger a new query when building is not present: 
  # SELECT  "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1  [["address", "123 my street"]]
  if user.building
    puts 'User has building'
  else
    puts 'User has no building' 
  end

end

用户类别:

class User < ActiveRecord::Base
  belongs_to :building, foreign_key: 'residence_id'
end

有没有办法在不触发额外查询的情况下检查用户的建筑关联是否存在?


在铁路 4.2.0 / POSTGRES


更新:

感谢@BoraMa 整理了这个test。看起来我们在最近的 Rails 版本中得到了不同的行为:

输出(轨道 4.2.0):

User 1 has building
User 2 has building
User 3 has no building
D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- :   Building Load (0.2ms)  SELECT  "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1  [["id", 123]]
User 4 has no building

输出(轨道 4.2.6)

User 1 has building
User 2 has building
User 3 has no building
User 4 has no building

输出(轨道 5.0.0)

User 1 has building
User 2 has building
User 3 has no building
User 4 has no building

外卖:

  • 此问题仅限于“悬空外键(即 Residence_id column 不是 nil 但没有对应的建筑对象)" (感谢@FrederickCheung)
  • 自 Rails 4.2.6 起,该问题已得到解决

【问题讨论】:

  • 我预计会出现类似于“ActiveRecord::AssociationNotFoundError: Association named 'buildings' was not found on User”的错误。你能确认查询语法以及用户->建立关联定义吗?
  • @messanjah 我个人希望user.building 在关联不存在的情况下返回 nil,这就是后续调用中发生的情况,但在第一次调用时它总是会触发 SQL 查询。跨度>
  • 奇怪,我没有观察到这种行为——我的 nil 关联只是返回 nil,没有进一步的查询。您在问题中给出的代码确实是该问题的最小工作示例吗?
  • 您是否有“悬空”外键(即 Residence_id 列不为零但没有对应的建筑对象)?
  • @Yarin,请参阅this gist 进行最少的可运行测试。我在带有 Postgres 的 Rails 4.2.6 上试过这个,仍然看不到其他查询。请尝试在您的系统上运行它并告诉我们它是否仍然存在错误,如果没有,请尝试更新它,以便它实际执行多余的查询。也许还发布两个表的架构(但我自己不确定它是否相关)。

标签: sql ruby-on-rails ruby ruby-on-rails-4 rails-activerecord


【解决方案1】:

听起来您在 Active Record 中被 bug 咬了,这已在 rails 4.2.3 中修复。

在列为零的情况下,Active Record 已经知道它甚至不需要尝试加载关联的对象。其余情况是受此错误影响的情况

【讨论】:

  • 为了胜利!很好的发现,感谢您的所有帮助,这让我很生气
  • 啊,太晚了!但你应得的,弗雷德里克 :) 很高兴我们都解决了这个有趣的错误。
【解决方案2】:

似乎是一个错字,请注意building 而不是buildingsUser.includes(:building).references(:buildings)

这应该会触发对每个关联和表使用AS tX_rY 格式的大查询。

【讨论】:

  • 感谢指出错字,但这并不能解决问题
【解决方案3】:

似乎从 Rails 4.1 开始,与#includes 的隐含程度存在潜在冲突,请参阅以下open issue

这段代码没有经过语法测试,但我会尝试两种方法:

1/ 隐式预加载

users = User.eager_load(:building).preload(:buildings)

2/ 分离出两种类型的用户,即附加建筑物的用户,这意味着您甚至无需尝试预加载建筑物,从而消除了效率低下。

users = User.includes(:building).where.not(residence_id: nil).references(:buildings)

users.each do | user|
     puts "User has building: #{user} #{user.building}"
end

# No additional references needed to be eager-loaded.
users = User.where(residence_id: nil)
users.each do | user |
     puts "#{User} has no building."
end

【讨论】:

  • 感谢您的建议。 #1 没有改变结果——初始查询是相同的,访问缺失关联时的行为也是如此。至于#2,将其拆分为两个单独的查询将适用于这个有限的示例,但在我们需要对整个查询执行过滤/分页操作的实际应用程序中不是一个选项
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-20
  • 1970-01-01
相关资源
最近更新 更多