计算原子FindAndModify操作中的最大值

时间:2011-12-14 17:09:51

标签: mongodb

我试图弄清楚是否有可能在MongoDB中推送一个元素并在同一时间(原子FindAndModify操作)更新数组中元素的最大值。

示例:

{
  "_id": "...",
  "max_value": 10,
  "values": [2, 10, 6]
}

插入20之后,结果将是:

{
  "_id": "...",
  "max_value": 20,
  "values": [2, 10, 6, 20]
}

将值20推送到values数组,并在同一原子操作中重新计算max_value字段(为20)。

有可能吗?

1 个答案:

答案 0 :(得分:4)

编辑:经过进一步反思,我原来的回答是正确的,但却很浪费。具体来说,第一步不是必需的,所以这是一个修订版本:

您可以通过两个步骤模拟此过程:

  1. findAndModify包含_idmax_value $lte您当前正在尝试插入的值。由于_id是唯一的,您知道只有零个或一个文档可以匹配此查询 - 假设存在_id的文档,在max_value为{0}的情况下为零大于您插入的内容,以及小于或等于的内容。在更新中,$push为新值,$set max_value

  2. 当且仅当步骤#1失败时,再次使用_id查找和修改,并$push数组的新值。由于第1步失败,我们知道当前max_value大于新值,因此我们可以忽略它,只需$push新值。

  3. 以下是实现此目的的Python代码示例:

    # the_id is the ObjectId of the document we want to modify
    # new_value is the new value to append to the list
    rslt1 = rslt2 = None
    
    rslt1 = db.collection.find_and_modify(
        {'_id': the_id, 'max_value': {'$lte': new_value}},
        {'$push': {'array': new_value}, '$set': {'max_value': new_value}})
    
    if rslt1 is None:
        rslt2 = db.collection.find_and_modify(
            {'_id': the_id},
            {'$push': {'array': new_value}})
    
    # only one of these will be non-None; this
    # picks whichever is non-None and assigns
    # it to rslt
    rslt = rslt1 or rslt2
    

    (这个原始答案有效,但上面的更新版本效率更高。)

    您可以通过三个步骤模拟此过程:

    1. findAndModify包含给定_id 的文档,其中max_value $gt是您尝试插入的当前值。由于_id是唯一的,您知道只有零个或一个文档可以匹配此查询 - 假设存在_id的文档,在max_value为{0}的情况下为零少于你插入的内容,以及一个更大的内容。此findAndModify的更新部分将$push数组的新值。

    2. 当且仅当步骤#1失败时,再次使用_idmax_value $lte查找当前正在尝试插入的值的FindAndModify。在更新中,$push为新值,$set max_value

    3. 当且仅当步骤#2失败时,再次使用_id查找和修改,并$push数组的新值。这包括步骤#1和#2之间的情况,另一个线程将max_value提升到大于您当前插入的值的值。

    4. 以下是实现此目的的Python代码示例:

      # the_id is the ObjectId of the document we want to modify
      # new_value is the new value to append to the list
      rslt1 = rslt2 = rslt3 = None
      rslt1 = db.collection.find_and_modify(
          {'_id': the_id, 'max_value': {'$gt': new_value}},
          {'$push': {'array': new_value}})
      
      if rslt1 is None:
          rslt2 = db.collection.find_and_modify(
              {'_id': the_id, 'max_value': {'$lte': new_value}},
              {'$push': {'array': new_value}, '$set': {'max_value': new_value}})
      
      if rslt1 is None and rslt2 is None:
          rslt3 = db.collection.find_and_modify(
              {'_id': the_id},
              {'$push': {'array': new_value}})
      
      # only one of these will be non-None; this
      # picks whichever is non-None and assigns
      # it to rslt
      rslt = rslt1 or rslt2 or rslt3