在Ruby中,如何将范围拆分为批处理操作的块?

时间:2015-07-01 16:17:55

标签: ruby-on-rails ruby

对于所有Ruby高尔夫球手和体操运动员来说,这应该是一个有趣的小谜题。

问题:我有数以百万计的SQL数据库记录,我需要以反向ID顺序批量转移到NoSql数据库(大约1000个左右)(因此find_each不是有效的解决方案)。

这将在小型服务器上运行,因此我不想一次将所有ID保留在内存中,只需要一个块的ID。

我希望在我的代码库中使用这些内容:

chunkify max_id, step_size do |ids|
  copy_to_nosql SqlTable.where(id: ids)
end

你能写chunkify吗?

解决方案不应重复任何ID,它应覆盖所有ID,第一个产生的数组应包含max_id,返回的最低ID应为1.

PS。我正在使用Rails框架,所以请随意使用Rails特定的语言扩充。

PPS。我不想使用像'limit'和'offset'这样的数据库驱动的解决方案,至少我的数据库实现,随着偏移的增加,查询需要更长的时间来处理。

修改

这是一个有效的解决方案。任何提高可读性,简洁性或效率的替代方案都是最受欢迎的:

  # yields arrays of ints (chunks), of [chunk_size] or lower length, which,
  # when added together, would cover all values from 0 to [top_value], exactly
  # once each. The highest value 'chunk' is provided first.
  #
  # > chunkify(100, 10) { |chunk| puts chunk.inspect }
  # [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
  # [81, 82, 83, 84, 85, 86, 87, 88, 89, 90]
  # ...
  # [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  # => 100
  # > chunkify(27, 8) { |chunk| puts chunk.inspect }
  # [20, 21, 22, 23, 24, 25, 26, 27]
  # [12, 13, 14, 15, 16, 17, 18, 19]
  # [4, 5, 6, 7, 8, 9, 10, 11]
  # [1, 2, 3]
  # => 27
  def chunkify(top_value, chunk_size)
    top_value.step(1, -chunk_size) do |i|
      yield ((i-chunk_size > 0 ? i-chunk_size+1 : 1)...i+1).to_a
    end
  end

2 个答案:

答案 0 :(得分:2)

你在寻找像这样简单的东西吗?

def chunkify(max_val,chunk_size);
  max_val.downto(1).each_slice(chunk_size) {|chunk| yield chunk.reverse }
end 

chunkify(100,10) {|c| puts c.inspect }
  #[91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
  #[81, 82, 83, 84, 85, 86, 87, 88, 89, 90]
  #[71, 72, 73, 74, 75, 76, 77, 78, 79, 80]
  #[61, 62, 63, 64, 65, 66, 67, 68, 69, 70]
  #[51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
  #[41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
  #[31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
  #[21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
  #[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
  #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  #=> nil

正如@SergioTulentsev所指出的那样,这似乎微不足道。

反向效率也会降低,与递减顺序一样有效,因为SQL不关心IN()子句的顺序。

答案 1 :(得分:1)

ActiveRecord提供了一种find_each方法,可用于加载小批量记录并对结果集执行特定操作。

  

循环遍历数据库中的一组记录(例如,使用all方法)是非常低效的,因为它会尝试一次实例化所有对象。

     

在这种情况下,批处理方法允许您批量处理记录,从而大大减少内存消耗。

     

find_each方法使用find_in_batches批量大小为1000(或:batch_size选项指定)。

您可能希望向要迁移的列添加新的布尔字段,以确保将查询过滤到仅processed为false的记录。这样,即使进程由于某种原因而死亡,您也可以恢复它,并且不会从头开始重启。