猫鼬-如何以增量方式减少/减少另一个子文档数组中的子文档数组中的值

时间:2020-05-30 08:28:13

标签: mongodb mongoose aggregation-framework

这很棘手,

我有这个样本文件

[{
  _id: '111',
  products: [{
    _id: 'productId',
    items: [{
      _id: 'aaa',
      quantity: 5
    }, {
      _id:'bbb',
      quantity: 8
    }]
  }]
}]

现在,当我购买商品时,我需要从商品数组中的第一个减少到第二,第三等的数量减少数量...

这就是说,当我购买一个商品时,我需要在商品数组的第一个元素中减少数量,并且如果要减少的数量超过了,则应该减少第二个商品数组中的剩余数量。 ..等等

例如,如果我购买了3件商品,则它在第一个数组元素中应减去3件商品,并且结果商品数组应为

...
    items: [{
      _id: 'aaa',
      quantity: 2
    }, {
      _id: 'bbb',
      quantity: 8
    }]

另一方面,如果我要购买7件商品,则应该减少第一个数组元素中的所有5件商品,然后从第二个元素中删除2件商品,因此最终的数组就像

...
    items: [{
      _id: 'aaa',
      quantity: 0
    }, {
      _id: 'bbb',
      quantity: 6
    }]

我尝试过的内容:

我试图像这样使用findOneAndUpdate,只是为了到达items数组,但我不知道我应该如何继续进行item部分。

        Shop.findOneAndUpdate(
            { _id: '111', products._id: 'productId' }
        )

1 个答案:

答案 0 :(得分:1)

尽管MongoDB v4.2(update pipeline)可以实现,您可以在其中基于文档的当前值更新文档。这将非常复杂,因为您实际上没有本地方法来保留局部变量(以跟踪已减去的项数)。您可以使用$reduce来保留该值,但是由于您已深度嵌套了数据结构,因此看起来非常复杂。如果可以在应用程序层中执行此操作会容易得多,但是如果您的文档在两者之间进行更改,则会遇到并发问题。

建议

  1. 更改数据结构,使其更平坦,可能会分成多个集合。在深层嵌套的项目中很难进行更新
  2. 等待MongoDB v4.4,您拥有$accumulator$function运算符,这些运算符可以保持状态并更灵活地使用JavaScript处理数据
  3. 使用MongoDB v4.2 update pipeline以及$reduce$cond。使用这种方法,您必须使用$reduce来维护状态,并根据状态应用数量值,因为您具有嵌套的数组结构,所以还必须使用$map
  4. 如上所述,您可以实现查询文档,在应用程序中进行更新并将更改放回MongoDB,但必须确保没有试图在读写之间更新文档的并发操作
  5. 更新:另一个解决方案是等待v4.4 $merge聚合阶段,该阶段可让您将聚合管道结果输出到同一集合。

更新:我尝试实现#3以显示复杂性。 answer对您其他问题的启发

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
                      ]
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    }
  }
])