如何快速批量更新postgres中的序列号

时间:2014-07-15 18:04:44

标签: sql ruby-on-rails postgresql

boss希望每个商家都有连续的订单号,从1000开始。

现在我循环浏览每个商家(使用ruby),并更新这样的订单:

#running all of this in a migration
add_column :orders, :order_seq, :integer


Merchant.find_each do |merchant|
  order_seq = 999
  merchant.orders.order(:ordered_at).find_each do |order|
    order.update_column(:order_seq, order_seq+=1)
  end
end

我计划在迁移期间运行此操作以将所有现有订单设置为根据ordered_at日期填充序列号。我在生产数据库的一个分支上测试了它,每个订单更新平均需要80毫秒。有近百万订单记录,这将导致太多的停机时间。

使用原生postgres有更快的方法吗?这将是一次性迁移,需要运行一次,并且没有其他任何事情同时进行。

我不是postgres专家,但有没有办法在每个merchant_id上使用999 + row_number()的窗口函数并将该row_number保存回order_seq列?

编辑:

使用@ Gorden-Linoff答案,但略有修改。我意识到我不需要在merchant_id上使用分区,因为只有一些活跃的商家需要这个,而不是整个表。此外,更新需要在订单表上,而不是商家表,而where子句只能使用id而不是merchant_id和ordered_at。

最终解决方案:

  Merchant.active.find_each(batch_size: 100) do |merchant|
    statement = "update orders set order_seq = o.seqnum + 999 " +
      "from (select o.id, row_number() " +
      " over (order by ordered_at) as seqnum from orders o where o.merchant_id = #{merchant.id}" +
      ") o where orders.id = o.id"
    ActiveRecord::Base.connection.execute(statement)
  end

结果是此操作需要10分钟来处理200个商家。旧方法在1小时内处理了大约10个商家。

1 个答案:

答案 0 :(得分:7)

我认为您可以使用可更新的子查询使用本机Postgres执行此操作:

update merchants
    set order_seq = m.seqnum + 999
    from (select m.*, row_number() over (order by ordered_at) as seqnum
          from merchants m
         ) m
    where merchants.merchant_id = m.merchant_id and
          merchants.ordered_at = m.ordered_at;

编辑:

如果您希望重新开始每个商家ID,请使用partition by

update merchants
    set order_seq = m.seqnum + 999
    from (select m.*, row_number() over (partition by merchant_id
                                         order by ordered_at
                                        ) as seqnum
          from merchants m
         ) m
    where merchants.merchant_id = m.merchant_id and
          merchants.ordered_at = m.ordered_at;