Mongodb 更新管道更新嵌套数组中元素的字段

Mongodb update pipeline update a field of a element inside a nested array

我有一个包含此类对象的集合。

{
  materials: {
    "m1": {
      inventory: [
        {
          price: 100,
          amount: 65
        }
      ]
    }
  }
}

如您所见,库存是层次结构深处的一个数组。我想更新它的 amount 字段。

我从客户端收到 material ID ("m1") 和库存索引 (0)。

我必须使用更新管道,因为我正在设置和取消设置此文档中的一些其他字段。

这是我试过的:

await products.findOneAndUpdate(filters, [
  {
    $set: {
      "materials.m1.inventory.0.amount": 100,
    },
  },
]);

但它在第 0 个元素内创建了一个名为 0 的新字段,并在该对象内设置了 amount。所以生成的文档看起来像这样。

{
  materials: {
    "m1": {
      inventory: [
        {
          0: {
            amount: 100
          }
          price: 100,
          amount: 65
        }
      ]
    }
  }
}

而我想要的是:

{
  materials: {
    "m1": {
      inventory: [
        {
          price: 100,
          amount: 100
        }
      ]
    }
  }
}

我确定要更新数组中哪个元素的唯一方法是它的索引。

我正在使用 nodejs mongodb 驱动程序。如何为此更新管道编写 $set 阶段?

我认为没有其他直接使用聚合管道更新特定索引位置元素的方法,

  • $range 生成从 0 到 materials.m1.inventory array
  • 长度的数组
  • $map 迭代上述范围数组的循环
  • $cond 检查当前元素是否为 0 然后转到更新部分,否则 return 来自 materials.m1.inventory 的元素,输入索引
  • $arrayElemAtmaterials.m1.inventory
  • 获取特定元素
  • $mergeObjects 将当前对象与更新后的 amount 合并 属性
await products.findOneAndUpdate(filters, [
  {
    $set: {
      "materials.m1.inventory": {
        $map: {
          input: {
            $range: [0, { $size: "$materials.m1.inventory" }]
          },
          in: {
            $cond: [
              { $eq: ["$$this", 0] }, // 0 position
              {
                $mergeObjects: [
                  { $arrayElemAt: ["$materials.m1.inventory", "$$this"] },
                  { amount: 100 } // input amount
                ]
              },
              { $arrayElemAt: ["$materials.m1.inventory", "$$this"] }
            ]
          }
        }
      }
    }
  }
]);

Playground

findOneAndUpdate 使用聚合管道的问题是您只能使用 $set$unset$replaceWith(及其别名)。似乎 $set$addFields 无法覆盖数组内的对象,因此您可以简单地根据需要的更改重新创建整个数组。您基本上被迫像这样构建查询:

db.collection.update(filters,
[
  {
    "$addFields": {
      "materials.m1.inventory": {
      ...
      }
    }
  }
])

这是我的实现,但可以有更直接的方法(参见替换单对象数组的编辑):

db.collection.update(filters,
[
  {
    "$addFields": {
      "materials.m1.inventory": {
        $concatArrays: [
          [
            {
              price: {
                $getField: {
                  field: "price",
                  input: {
                    $first: "$materials.m1.inventory"
                  }
                }
              },
              amount: 100
            }
          ],
          {
            $slice: [
              "$materials.m1.inventory",
              1,
              {
                $size: "$materials.m1.inventory"
              }
            ]
          }
        ]
      }
    }
  }
])

我正在使用 $concatArraysconcat:

  1. 具有单个对象的数组,该对象位于索引 0 但 amount 字段设置为 100
  2. inventory 数组的切片,从索引 1 开始。

显然,如果索引不为 0,则必须采用第一个切片 inventory[0:index]、单个对象数组(参见第 1 点)和切片 inventory[index + 1:]

Here 演示。


编辑: @turivishal 提出的 $mergeObjects 部分绝对比我的单对象数组好:无论你有 2 个还是 20 个字段,代码都是一样的。