我试图弄清楚是否有可能在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)。
有可能吗?
答案 0 :(得分:4)
编辑:经过进一步反思,我原来的回答是正确的,但却很浪费。具体来说,第一步不是必需的,所以这是一个修订版本:
您可以通过两个步骤模拟此过程:
findAndModify包含_id
和max_value
$lte
您当前正在尝试插入的值。由于_id
是唯一的,您知道只有零个或一个文档可以匹配此查询 - 假设存在_id
的文档,在max_value
为{0}的情况下为零大于您插入的内容,以及小于或等于的内容。在更新中,$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
(这个原始答案有效,但上面的更新版本效率更高。)
您可以通过三个步骤模拟此过程:
findAndModify包含给定_id
和的文档,其中max_value
$gt
是您尝试插入的当前值。由于_id
是唯一的,您知道只有零个或一个文档可以匹配此查询 - 假设存在_id
的文档,在max_value
为{0}的情况下为零少于你插入的内容,以及一个更大的内容。此findAndModify的更新部分将$push
数组的新值。
当且仅当步骤#1失败时,再次使用_id
和max_value
$lte
查找当前正在尝试插入的值的FindAndModify。在更新中,$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