计算原子查找和修改操作中的最大值



我试图弄清楚是否可以在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. 查找和修改与_idmax_value $lte您当前尝试插入的值。由于_id是唯一的,因此您知道只有零个或一个文档可以与此查询匹配 - 假设存在具有该_id的文档,则在max_value大于要插入的内容的情况下为零,在小于或等于的情况下为1。在更新中,$push新值,然后$set max_value .

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

下面是实现这一点的示例 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. findAnd使用给定_id修改文档,并max_value $gt您尝试插入的当前值。由于_id是唯一的,因此您知道只有零个或一个文档可以与此查询匹配 - 假设存在具有该_id的文档,则在max_value小于要插入的内容的情况下为零,在大于的情况下为1。此查找和修改的更新部分会将新值$push到数组。

  2. 当且仅当步骤 #1 失败时,使用 _id 再次查找和修改并max_value $lte您当前尝试插入的值。在更新中,$push新值,然后$set max_value

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

下面是实现这一点的示例 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

相关内容

最新更新