Home > Mobile >  Conditionally update item or delete item in one query in mongoose
Conditionally update item or delete item in one query in mongoose

Time:12-26

So, what I'm trying to achieve here is two things. First one is to decrement the quantity of a product. Second is that if that product quantity is 0 then delete that product. So I'm trying to set a condition that if product quantity is 0 then delete that product otherwise simply decrement the quantity.

Here is my cart document:

enter image description here

And here is my function:

const cart = await Cart.findOneAndUpdate(
    { userID: req.body.userID, "items.productID": req.body.productID },
    {$lt: ['items.productID', 0] ? { $pull: { items: { productID: req.body.productID } } } : { $inc: { 'items.$.quantity': -1 } }},
    { new: true }
);

I have tried to achieve that with ternary operator but it didn't worked. Can anybody help me with this please? Thank you.

CodePudding user response:

You cannot use a ternary operator in your query. Not only is this not supported in MongoDB, it will also simply be evaluated by node.js, not by MongoDB itself. In fact, conditional update operations of the nature you're describing are themselves not something that MongoDB is capable of handling.

Your best bet is to perform two queries, where the first one performs the decrement operation and the second pulls any items out that have a quantity less than or equal to 0:

const cartAfterDecrement = await Cart.findOneAndUpdate(
    { userID: req.body.userID, "items.productID": req.body.productID },
    { $inc: { 'items.$.quantity': -1 } },
    { new: true }
);

const cartAfterPull = await Cart.findOneAndUpdate(
    { userID: req.body.userID, items: { $elemMatch: {
        productID: req.body.productID,
        quantity: { $lte: 0 }
    } } },
    { $pull: {items: { quantity: { $lte: 0 } } } },
    { new: true }
);

const cart = cartAfterPull ? cartAfterPull : cartAfterDecrement;

If you wish to allow products with quantities equal to 0, then modifying the second query is a trivial matter.

The only downside to this approach is that there will be a natural, very slight delay between these two updates, which may result in a very rare case of a race condition occurring where a cart is returned and contains an item with a quantity less than or equal to 0. In order to avoid this, you will need to either filter these values out with server code or by retrieving your cart(s) using aggregation, or you will need to make use of transactions to ensure that the two operations occur in sequence without the document being retrieved between the two write operations.

With that said, constructing the appropriate array filtering logic or transaction management is beyond the scope of this question, so will not be included in this answer.

CodePudding user response:

Query

  • you can do it with a pipeline update ( >= MongoDB 4.2)
  • find the user that has that productID
  • reduce the items, to an array with item but
    • if productID not the one i look for, keep it as it is
    • else if quantity=0 remove it(don't add it to the reduced array)
    • else keep it with quantity-1

*findAndModify accepts pipeline also, for nodejs driver you must find the method that wraps this command, or run the command directly

Test code here

update(
{"userID": req.body.userID,
 "items.productID": req.body.productID},
[{"$set":
  {"items":
   {"$reduce":
    {"input":"$items",
     "initialValue":[],
     "in":
     {"$switch":
      {"branches":
       [{"case":{"$ne":["$$this.productID", req.body.productID]},
         "then":{"$concatArrays":["$$value", ["$$this"]]}},
        {"case":{"$gt":["$$this.quantity", 0]},
         "then":
         {"$concatArrays":
          ["$$value",
           [{"$mergeObjects":
             ["$$this", {"$subtract":["$this.quantity", 1]}]}]]}}],
       "default":"$$value"}}}}}}])
  • Related