Say I have a collection of shops:
[
{
"_id": 0,
"items": [
{"_id": 3, "price": 10}
]
},
{
"_id": 1,
"items": []
},
{
"_id": 2
}
]
I want to update the price of item 3 in a shop to 30, inserting a new item in the shop if no item exists in that shop and/or inserting a new shop if necessary:
- shop 0: simply updates the item
- shop 1: appends
{"_id": 3, "price": 30}to shop 1'sitems - shop 2: sets shop 2's
itemsto[{"_id": 3, "price": 30}] - shop 3: inserts the document
{"_id": 3, "items": [{"_id": 3, "price": 30}]}
In other terms, I want to:
collection.updateOne({_id: <shopId>, 'items._id': 3}, {$set: {'items.$.price': 30}})if the item exists (shop 0)collection.updateOne({_id: <shopId>}, {$push: {items: {_id: 3, price: 30}}}, {upsert: true})if it doesn't (shops 1 to 3)
This is kind of like $ with upsert, but the documentation clearly states that upsert cannot be used with it:
Do not use the positional operator
$with upsert operations because inserts will use the$as a field name in the inserted document.
$[<identifier>] doesn't work either:
If the upsert operation did not include an exact equality match and no matching documents were found to update, the upsert operation would error.
Is there a way to do this without having to hit the database multiple times?
Things I've tried:
// 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}
)
As D. SM said, you can use
bulkWriteto do multiple operations in one go: