我试图弄清楚是否可以在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(。
可能吗?
编辑:经过进一步思考,我最初的答案是正确的,但浪费了。具体来说,第一步不是必需的,所以这里有一个修订版本:
您可以通过两个步骤模拟此过程:
-
查找和修改与
_id
并max_value
$lte
您当前尝试插入的值。由于_id
是唯一的,因此您知道只有零个或一个文档可以与此查询匹配 - 假设存在具有该_id
的文档,则在max_value
大于要插入的内容的情况下为零,在小于或等于的情况下为1。在更新中,$push
新值,然后$set
max_value
. -
当且仅当步骤 #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
(这个原始答案有效,但上面的更新版本更有效。
您可以通过三个步骤模拟此过程:
findAnd使用给定
_id
修改文档,并max_value
$gt
您尝试插入的当前值。由于_id
是唯一的,因此您知道只有零个或一个文档可以与此查询匹配 - 假设存在具有该_id
的文档,则在max_value
小于要插入的内容的情况下为零,在大于的情况下为1。此查找和修改的更新部分会将新值$push
到数组。当且仅当步骤 #1 失败时,使用
_id
再次查找和修改并max_value
$lte
您当前尝试插入的值。在更新中,$push
新值,然后$set
max_value
。当且仅当步骤 #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