$ 带 upsert 的位置更新运算符

$ positional update operator with upsert

假设我有一系列商店:

[
  {
    "_id": 0,
    "items": [
      {"_id": 3, "price": 10}
    ]
  },
  {
    "_id": 1,
    "items": []
  },
  {
    "_id": 2
  }
]

我想将商店中商品 3 的 price 更新为 30,如果该商店中没有商品,则在商店中插入一个新商品 and/or 如有必要,插入一个新商店:

换句话说,我想:

这有点像 $upsert,但是 the documentation 明确指出 upsert 不能与它一起使用:

Do not use the positional operator $ with upsert operations because inserts will use the $ as a field name in the inserted document.

$[<identifier>] 也不行:

If the upsert operation did not include an exact equality match and no matching documents were found to update, the upsert operation would error.

有没有办法不用多次访问数据库就可以做到这一点?


我尝试过的事情:

// Updating the path 'items' would create a conflict at 'items'
collection.updateOne(
  {_id: 0, 'items._id': 3},
  {$set: {'items.$.price': 30}, $setOnInsert: {items: []}}
)

// $[<identifier>] with upsert doesn't work
// The path 'items' must exist in the document in order to apply array updates.
collection.updateOne(
  {_id: 2},
  {$set: {'items.$[item].price': 30}},
  {arrayFilters: [{'item._id': 3}], upsert: true}
)

// Updating the path 'items' would create a conflict at 'items'
collection.updateOne(
  {_id: 0},
  {$set: {'items.$[item].price': 30}, $setOnInsert: {items: []}},
  {arrayFilters: [{'item._id': 3}], upsert: true}
)

作为D. SM said, you can use bulkWrite一次做多个操作:

const shopId = 0
collection.bulkWrite([
  // Remove any existing item
  {
    updateOne: {
      filter: {_id: shopId},
      update: {$pull: {items: {_id: 3}}}
    }
  },
  // Add the item with the updated price to the shop
  {
    updateOne: {
      filter: {_id: shopId},
      update: {
        $push: {items: {_id: 3, price: 30}}
      },
      upsert: true
    }
  }
])