【问题标题】:How to bulk update/upsert with mongoid/mongodb?如何使用 mongoid/mongodb 批量更新/upsert?
【发布时间】:2018-11-27 01:45:58
【问题描述】:

我有一个包含数百万 Orderdocuments 的数据库。我用以下方法批量插入它们:

Order.collection.insert([
                         {:_id=>BSON::ObjectId('5471944843687229cdfb0000'), :status=>"open", :name=> "Benny"},
                         {:_id=>BSON::ObjectId('5471944843687229cdfc0000'), :status=>"open", :name=> "Allan"}
                        ])

我经常需要更新订单上的status 属性。使用update_attribute 方法单独更新它们会效率低下。

如何批量更新多个 MongoDB 文档?

可以用以下“虚构”代码最好地描述所需的解决方案:

# IMPORTANT: The exemplified upsert method does not exist

Order.collection.upsert([
                         {:_id=>BSON::ObjectId('5471944843687229cdfb0000'), :status=>"closed"},
                         {:_id=>BSON::ObjectId('5471944843687229cdfc0000'), :status=>"some_other_status"}
                        ])

仅供参考,可能有类似的问题/答案in this SO post,但老实说,我没有遵循答案。

【问题讨论】:

  • 链接问题中的答案给出了一个很长的示例,但您要理解(并向后工作)的关键是:“{ update: Product.collection_name.to_s, updates: updates, ordered : false }" 这是一个更新命令,它需要多个更新指令。 “updates”参数是要进行更新的列表 - 向后查看代码以查看该批更新是如何构建的。
  • 嗨@AsyaKamsky,你能把它作为这个问题的更简短的答案吗?我只有 2 天的时间来奖励答案。

标签: ruby-on-rails ruby mongodb mongoid


【解决方案1】:

引用问题中的最佳答案可以简化为

id_status = [['5471944843687229cdfb0000','closed'], ...] 

bulk_order = id_status.map do |id, status| # Using array destructuration
  { update_one:
    {
      filter: { _id: id },
      update: { :'$set' => {
        status: status,
      }}
    }
  }
end
YourCollection.collection.bulk_write(bulk_order)

【讨论】:

    【解决方案2】:

    首先,您需要过滤Orders 以仅匹配orders_to_update 的ID。 您使用any_in Criteria method 过滤它们。然后使用update_all 批量更新它们。

    像这样:

    orders_to_update = [BSON::ObjectId('5471944843687229cdfb0000'), BSON::ObjectId('5471944843687229cdfc0000')]
    
    Order.any_in(id: orders_to_update).update_all(status: "closed")
    

    【讨论】:

    • @Anzeo:基本上,我们使用any_in Criteria 方法 (two.mongoid.org/docs/querying/criteria.html#any_in) 过滤那些匹配“orders_to_update” id 的订单,并使用update_all (@987654324) 批量更新所有订单@)
    • 感谢您的回答。尽管我的示例中的所有文档都应使用相同的值 (closed) 进行更新,但我的“虚构”示例要求提供一个现实生活中的解决方案,我可以在其中指定每个文档要更新的内容。所以例如其中一个文档可以更新为closed,而另一个文档可以更新为refunded
    • @ChristofferJoergensen - 单独处理这些更新是否可行?换句话说,批量更新所有应该更改为closed 的内容,然后批量更新所有应该更改为refunded 的内容。如果不是,那么您如何确定每个文档的状态应该是什么?我希望这些信息可以告知如何修改此答案。
    • 非常感谢您的提问。但是在更新的字段是例如的情况下一个关系(所以所有值都是unikke)不会节省时间。据我所知,唯一的可能性是能够定义一个 MongoDB ID 数组以及字段名称和字段值。
    【解决方案3】:

    这里真正的问题是更新。更新很慢,因为它需要读取、替换和更改文档。

    我在同一个问题上被阻止了很多天。我在 stackoverflow 和任何其他网站上都没有找到任何解决方案。因此,我编写了自己的解决方案。也许你会发现它不是很“干净”,但它的时间效果很好。

    解决方案在于销毁并重新创建此文档。销毁非常快,使用批量执行“collection.insert”创建新文档非常快。

    def get_orders(*params)
       Order.where(# some conditions).asc(:id)
    end
    
    namespace :my_collection_repairer do
    desc ""
    
      task update: :environment do
        all_orders = get_orders(# some conditions)
        while all_orders.count > 0
          num_docs = all_orders.count
          group_size = 10000
          num_groups = (Float(num_docs) / group_size).ceil
          puts "#{num_docs} documents found. #{num_groups} groups calculated."
    
          1.upto(num_groups) do |group|
            updated_order_list = []
            order_group = all_orders.page(group).per(group_size)
            puts "group #{group}"
    
            order_group.each do |order|
              updated_order = update_order(order) # this represents your custom update method
              updated_order_list << updated_order.as_document
              order.destroy
            end
    
            Order.collection.insert(updated_order_list)
            puts "Group #{group} updated."
          end
          all_orders = get_orders(# some conditions)
        end
      end
    end
    

    【讨论】:

      【解决方案4】:

      将更新或替换操作的 upsert 选项设置为 true,并具有以下 语法

       bulk.find( { status: "closed" } ).update( { $set: { status: "some_other_status" } } );
       bulk.execute();
      

      将多更新操作添加到批量操作列表。该方法更新现有文档中的特定字段。

      使用 Bulk.find() 方法指定确定要更新哪些文档的条件。 Bulk.find.update() 方法更新所有匹配的文档。要指定单个文档更新,请参阅 Bulk.find.updateOne()

       var bulk = db.collection.initializeUnorderedBulkOp();
       bulk.find( { status: "closed" } ).upsert().update(
      {
       $set: { status: "some_other_status"}
      }
      );
      bulk.execute();
      

      注意

      要为此操作指定 upsert: true,请使用 Bulk.find.upsert()。使用 Bulk.find.upsert(),如果没有文档匹配 Bulk.find() 查询条件,则更新操作仅插入单个文档。 希望这会有所帮助。

      【讨论】:

      • 谢谢@SUNDARRAJANK。但是您是否可以编辑或添加示例,以便它使用与问题中的示例值相对应的示例值?我很难理解Bulk 等所代表的含义。
      • 另外,即使我的示例中的所有文档都应该使用相同的值进行更新 (closed) 我的“虚构”示例要求提供一个现实生活中的解决方案,我可以在其中指定要更新的内容每个文件。所以例如其中一个文档可以更新为closed,而另一个文档可以更新为refunded
      • var bulk = db.collection.initializeUnorderedBulkOp();
      • 你好@SUNDARRAJANK。抱歉,我没有关注最后的评论。
      • bulk 是 mongo db 的内置 UnorderedBulk 数据,它存储用于插入、删除、更新、删除等操作的游标参考 docs.mongodb.org/manual/reference/method/js-bulk
      猜你喜欢
      • 2014-10-22
      • 1970-01-01
      • 1970-01-01
      • 2013-06-05
      • 2011-04-15
      • 1970-01-01
      • 2021-01-07
      • 1970-01-01
      • 2022-07-04
      相关资源
      最近更新 更多