3

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

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

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

我从客户端收到材料 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此更新管道的阶段?

4

2 回答 2

3

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

  • $range生成从 0 到数组长度的materials.m1.inventory数组
  • $map迭代上述范围数组的循环
  • $cond检查当前元素是否为 0,然后去更新部分,否则materials.m1.inventory通过输入索引返回元素
  • $arrayElemAt从获取特定元素materials.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"] }
            ]
          }
        }
      }
    }
  }
]);

操场

于 2021-08-25T14:31:02.360 回答
2

使用聚合管道的问题findOneAndUpdate在于您只能使用$set,$unset$replaceWith(及其别名)。似乎$set, or$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"
              }
            ]
          }
        ]
      }
    }
  }
])

我正在$concatArrays使用concat

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

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

这里演示。


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

于 2021-08-25T15:50:14.780 回答