I have a Cart schema in Mongoose including CartItems as an array of objects.
{
_id: string;
items: [
{
product: string;
options: {
size: string;
color: string;
}
quantity: number;
}
]
}
In this case, is it possible to push an item when product and options doesn't match or add quantity when it does?
Here is the code that I have tried which does not work.
Cart.findByIdAndUpdate(_id, {
items: {
$cond: [
{
// Does not work
$elemMatch: {
product,
"options.color": options.color,
"options.size": options.size,
},
},
{
// Should add quantity where it is matched.
},
{
$push: {
product,
productName,
options,
quantity,
},
},
],
},
});
CodePudding user response:
Query
- pipeline update requires MongoDB >= 4.2
newProduct
is the product you want to add (a js variable)- check if product already exists => add not-exist? field
- if not-exists add it in the end
- else map to find it and update the quantity
- unset the 2 fields
newProduct
andnot-exists
*it does 2 array reads, alternative could be to use $reduce
but if you have many products $concatArrays
is slow to be inside a reduce, so this is faster solutions (even if reduce would read the array 1 time only)
*you need a method to do update with pipeline, i don't know if mongoose is updated to support it, we are MongoDB 5 so i guess it will be(java is), in worst case you can use updateCommand
and call it with runCommand(...)
update({"_id" : "1"},
[{"$set":
{"newProduct":
{"product":"p1",
"options":{"size":"s1", "color":"c1"},
"quantity":1}}},
{"$set":
{"not-exists?":
{"$eq":
[{"$filter":
{"input":"$items",
"cond":
{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]}}},
[]]}}},
{"$set":
{"items":
{"$cond":
["$not-exists?", {"$concatArrays":["$items", ["$newProduct"]]},
{"$map":
{"input":"$items",
"in":
{"$cond":
[{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]},
{"$mergeObjects":
["$$this", {"quantity":{"$add":["$$this.quantity", 1]}}]},
"$$this"]}}}]}}},
{"$unset":["not-exists?", "newProduct"]}])
Query2
- if you don't want to use update pipeline you can do it with more queries
Check if exists
db.collection.find({
"_id" : "1",
"items": {
"$elemMatch": {
"product": "p1",
"options": {
"size": "s1",
"color": "c1"
}
}
}
})
If not exists
db.collection.update({
"_id": "1"
},
{
"$push": {
"items": "NEWITEM" //put here the new object
}
})
else If exists
db.collection.update({"_id" : "1"},
{
"$inc": {
"items.$[i].quantity": 1
}
},
{
arrayFilters: [
{
"i.product": "p1",
"i.options.size": "s1",
"i.options.color": "c1"
}
]
})