【问题标题】:Best way for getting the count of a query set after updating elements更新元素后获取查询集计数的最佳方法
【发布时间】:2020-01-07 11:02:26
【问题描述】:

我正在寻找获取 Rails 中查询集大小的最佳方法。但是,元素是循环更新的,我需要在更新之前计算元素的数量。这是一些示例(错误!)代码。

p = participations.where(invited_at: nil).limit(50)
p.each do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation.save
end

DoStuff() if p.count > 0

此代码不起作用,因为p.count 处的调用创建了一个新的数据库查询,该查询不考虑循环中已更新的记录。因此,如果少于 50 条记录,则全部更新,不会调用 DoStuff()

Rails 中最惯用的方式来处理这个问题:

  1. if p.count部分移出循环,只有在有记录时才进入循环?
  2. p.count 替换为p.size(如果我对size 的理解正确,这不会引起任何额外的查询)
  3. 计算循环中的迭代次数,然后使用该数字

我感觉 1 在 ruby​​ 中最地道,但我对这门语言没有太多经验。

编辑:改进的示例更接近原始代码。

编辑

问题不在于对循环中的参与者执行的更新查询。这些查询应该是单独的查询,以跟踪哪些参与已经被处理,即使出现错误。相反,问题是应该调用DoStuff(),只要在循环中处理了任何记录。但是,由于count 执行新查询AFTER记录已被处理,如果要处理的元素少于 50 个,则将全部更新并且不会调用 DoStuff()。 p>

【问题讨论】:

  • 但是,因为 count 会在处理完记录后执行新的查询,...,所以使用size,不是吗? p 已经在内存中,如果没有改变它的值,那么 DoStuff 的使用应该不会受到影响。

标签: mysql ruby-on-rails ruby


【解决方案1】:

这就是countsize 之间的区别,size 将返回加载对象的数量,如果它们已加载,否则将回退到count。所以最简单的解决方法是将count 替换为size

但是 each 返回它迭代的集合,所以你可以这样做 DoStfuff if p.each(&block).any? (如果你有多行块看起来不漂亮)

一种更简洁的方法,即无需审阅者知道sizecount 之间的区别,也无需检查每个是否导致包含至少一个元素的集合,是将您的代码封装在一个方法中并添加一个保护子句。

def process_invitations
  p = participations.where(invited_at: nil).limit(50)
  return if p.none?

  p.each do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation.save
  end

  DoStuff()
end

您甚至可以取消限制并使用p.first(50).each do

【讨论】:

    【解决方案2】:

    将if p.count部分移出循环,只有有记录才进入循环?

    如果没有记录,.each 不会进入循环。如果你想证明尝试:

    MyModel.none.each { puts "Hello world" }
    

    如果您想要一种更惯用的方式,那么如果您关心结果,请不要使用#each#each 只应在您只关心迭代的副作用时使用。

    改为使用#map 或许多其他迭代方法之一。

    def process_invitations
      p = participations.where(invited_at: nil).limit(50)
      p.map do |participation|
       # Invite may raise an exception, but also contains operations that
       # cannot be undone
       participation.invite()
       participation.invited_at = Time.zone.now
       participation.save
      end.reject.yeild_self do |updates|
        # run do_stuff if any of the records where updated
        do_stuff() if updates.any?
      end
    end
    

    或者,例如,如果您只想 do_stuff 记录更新的地方:

    def process_invitations
      p = participations.where(invited_at: nil).limit(50)
      p.map do |participation|
       # Invite may raise an exception, but also contains operations that
       # cannot be undone
       participation.invite()
       participation.invited_at = Time.zone.now
       participation if participation.save
      end.reject.yeild_self do |updated|
        do_stuff(updated) if updated.any?
      end
    end
    

    do_stuff 到每个更新的记录:

    def process_invitations
      p = participations.where(invited_at: nil).limit(50)
      p.map do |participation|
       # Invite may raise an exception, but also contains operations that
       # cannot be undone
       participation.invite()
       participation.invited_at = Time.zone.now
       participation if participation.save
      end.reject.map do |record|
        do_stuff(record)
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-24
      • 2010-10-07
      • 2010-10-11
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 2010-09-06
      相关资源
      最近更新 更多