Хотя это возможно с MongoDB v4.2 ( конвейер обновления ), где вы можете обновлять документы на основе их текущего значения. Это было бы очень сложно, поскольку у вас действительно нет собственного способа хранить локальные переменные (чтобы отслеживать, сколько элементов вы уже вычли). Вы можете использовать $reduce
, чтобы сохранить это значение, но это будет выглядеть очень сложным, поскольку у вас есть глубоко вложенные структуры данных. Будет намного проще, если вы сможете сделать это на уровне приложения, но тогда у вас возникнут проблемы с параллелизмом, если ваш документ изменится между ними.
Предложения
- Измените структуру данных, чтобы быть более плоским, возможно, разделить на несколько коллекций. Сложно выполнять обновления в глубоко вложенных элементах
- Подождите, пока MongoDB v4.4, где у вас есть операторы
$accumulator
и $function
, которые могут сохранять состояние и более гибко обрабатывает ваши данные, используя JavaScript - Используйте MongoDB v4.2 конвейер обновления вместе с
$reduce
и $cond
. При таком подходе вы должны поддерживать состояние, используя $reduce
, и применять значение количества в соответствии с состоянием, поскольку у вас есть вложенные структуры массивов, вам также придется использовать $map
. - Как упоминалось выше, вы можете реализовать запрос документа, обновить в своем приложении и вернуть изменения в MongoDB, но вы должны убедиться, что нет параллельной операции, которая пытается обновить ваш документ между чтением и записью.
- Обновление: другое решение - дождаться стадии агрегации v4.4
$merge
, которая позволяет вывести результат конвейера агрегации в ту же коллекцию.
Обновление: я попытался реализовать №3, чтобы показать сложность. Вдохновение взято из ответа на ваш другой вопрос
Shop.updateOne({
"_id": '111', "products._id": "productId" // don't forget to replace "productId"
}, [{
"$set":{
"products":{
"$map":{
"input":"$products", // iterate through products, we can not update inside the array directly as "arrayFilters" update option isn't available in pipeline update
"in":{
"$cond":[
{
"$ne":[
"$$this._id",
"productId" // don't forget to replace "productId"
]
},
"$$this",
{
"$mergeObjects":[
"$$this",
{
"items":{
"$reduce":{
"input":"$$this.items",
"initialValue":{ // initialise accumulator
"acc": 7, // the number you want to subtract
"items":[]
},
"in":{
"acc":{
"$subtract":[
"$$value.acc",
{
"$min":[
"$$this.quantity",
"$$value.acc"
]
}
]
},
"items":{
"$concatArrays":[
"$$value.items",
[
{
"$mergeObjects":[
"$$this",
{
"quantity":{
"$subtract":[
"$$this.quantity",
{
"$min":[
"$$this.quantity",
"$$value.acc"
]
}
]
}
}
]
}
]
]
}
}
}
}
}
]
}
]
}
}
}
}
}, // at this stage, you'll have all information you need, add the next stage only if you want it in the same structure
{
"$set":{
"products":{
"$map":{
"input":"$products",
"in":{
"$cond":[
{
"$ne":[
"$$this._id",
"productId"
]
},
"$$this",
{
"$mergeObjects":[
"$$this",
{
"items":{
"$ifNull":[
"$$this.items.items",
"$$this.items"
]
}
}
]
}
]
}
}
}
}
}
])
Обновление # 2 «Более короткая» версия, которая требует только одного этапа, с использованием массива вместо объекта для сохраните аккумулятор.
Shop.updateOne({
"_id": '111', "products._id": "productId" // don't forget to replace "productId"
}, [
{
"$set": {
"products": {
"$map": {
"input": "$products",
"in": {
"$cond": [
{
"$ne": [
"$$this._id",
"productId" // don't forget to replace "productId"
]
},
"$$this",
{
"$mergeObjects": [
"$$this",
{
"items": {
"$arrayElemAt": [
{
"$reduce": {
"input": "$$this.items",
"initialValue": [
7, // the number you want to subtract
[]
],
"in": [
{
"$subtract": [
{
"$arrayElemAt": [
"$$value",
0
]
},
{
"$min": [
"$$this.quantity",
{
"$arrayElemAt": [
"$$value",
0
]
}
]
}
]
},
{
"$concatArrays": [
{
"$arrayElemAt": [
"$$value",
1
]
},
[
{
"$mergeObjects": [
"$$this",
{
"quantity": {
"$subtract": [
"$$this.quantity",
{
"$min": [
"$$this.quantity",
{
"$arrayElemAt": [
"$$value",
0
]
}
]
}
]
}
}
]
}
]
]
}
]
}
},
1
]
}
}
]
}
]
}
}
}
}
}
])