2011-03-22 38 views
23

在我创建的rails 2应用程序中,我需要更新具有特定属性的记录集合。我有一个命名范围来查找集合,但我必须遍历每个记录以更新属性。与其让一个查询更新几千条记录,我不得不做几千条查询。在rails中一次更新几条记录

我已经发现迄今是像Model.find_by_sql("UPDATE products ...)

这感觉很初级,但我GOOGLE了,环顾四周SO,并没有发现我的答案。

为清楚起见,我有什么是:

ps = Product.last_day_of_freshness 
ps.each { |p| p.update_attributes(:stale => true) } 

我要的是:

Product.last_day_of_freshness.update_attributes(:stale => true) 

回答

40

这听起来像你正在寻找ActiveRecord::Base.update_all - 从文档:

如果所提供的条件与所提供的一组条件相匹配,则可以更新所有记录并给出详细信息,也可以提供限制和顺序该方法构造一个单独的SQL UPDATE语句并将其直接发送到数据库。它不会实例化相关模型,也不会触发Active Record回调或验证。

Product.last_day_of_freshness.update_all(:stale => true) 

其实,因为这是轨道2.x的(没有指定) - 该named_scope链接可能无法正常工作,您可能需要通过条件的命名范围为update_all第二个参数而不是将其链接到产品范围的末尾。

+1

update_all仅在Rails 3上可用 - 不幸的是Rails 2的问题。 – 2011-03-22 16:44:30

+2

@Taryn East不正确,'update_all'已从ActiveRecord :: Base移动到ActiveRecord :: rails 2.3.x和3.0之间的关系 - I已经更新了链接并添加了一条评论来澄清。 – 2011-03-22 16:46:07

+0

很酷,谢谢...没有意识到! – 2011-03-22 16:49:44

3

卢斯像update_all是最好的选择...虽然我会的情况下,保持我的哈克的版本,你很好奇:

您可以只使用纯OLE SQL做你想这样:

ps = Product.last_day_of_freshness 
ps_ids = ps.map(%:id).join(',') # local var just for readability 
Product.connection.execute("UPDATE `products` SET `stale` = TRUE WHERE id in (#{ps_ids)") 

请注意,这是依赖于数据库 - 您可能需要调整适合的引用样式。

0

对于需要更新大量记录(百万甚至更多)的用户,可以通过批量更新记录。

product_ids = Product.last_day_of_freshness.pluck(:id) 
iterations_size = product_ids.count/5000 

puts "Products to update #{product_ids.count}" 

product_ids.each_slice(5000).with_index do |batch_ids, i| 
    puts "step #{i} of iterations_size" 
    Product.where(id: batch_ids).update_all(stale: true) 
end 

如果你的表中有很多指标,它也将增加时间,这样的操作,因为它需要重建他们。在我的情况下,当我为表中的所有记录运行update_all时,大约有两百万和十二个索引,操作在一个多小时内没有完成。采用这种方法,开发env需要大约20分钟,生产需要大约4分钟。当然这应该在rake task或一些后台工作。