在猫鼬的一个查询中有条件地更新项目或删除项目

分享于2022年07月17日 mongodb mongoose node.js 问答
【问题标题】:在猫鼬的一个查询中有条件地更新项目或删除项目(Conditionally update item or delete item in one query in mongoose)
【发布时间】:2022-01-25 12:34:41
【问题描述】:

所以,我在这里想要实现的是两件事。第一个是减少产品的数量。其次,如果该产品数量为 0,则删除该产品。 所以我试图设置一个条件,如果产品数量为 0,则删除该产品,否则只需减少数量。

这是我的购物车文件:

Conditionally update item or delete item in one query in mongoose

这是我的功能:

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 }
);

我曾尝试使用三元运算符来实现这一点,但没有奏效。有人可以帮我吗?谢谢。


【解决方案1】:

您不能在查询中使用三元运算符。这不仅在 MongoDB 中不支持,它还将由 node.js 评估,而不是由 MongoDB 本身评估。实际上,您所描述的性质的条件更新操作本身并不是 MongoDB 能够处理的。

最好的办法是执行两个查询,其中第一个执行减量操作,第二个提取任何数量小于或等于 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;

如果您希望允许数量等于 0 的产品,那么修改第二个查询是一件小事。

这种方法的唯一缺点是这两次更新之间会有一个自然的、非常轻微的延迟,这可能会导致极少数情况下发生竞争条件,即退回购物车并包含具有数量的商品小于或等于 0。为了避免这种情况,您需要使用服务器代码或通过使用聚合检索您的购物车来过滤这些值,或者您需要使用事务来确保这两个操作按顺序发生,而不会在两次写入操作之间检索文档。

话虽如此,构建适当的数组过滤逻辑或事务管理超出了本问题的范围,因此不会包含在本答案中。

  • 只是给我两分钱:事务不会阻止竞争条件,它只提供原子性。根据他的网站获得的流量,竞争条件可能是正常的。锁定将是并发处理的建议。
  • @BrunoFarias 我可能弄错了,但是 MongoDB 的文档级写锁,结合单一事务提交和 MongoDB 的并发优化,不会确保这些写操作按顺序执行,之间没有读取他们优化查询吞吐量?如果不是,那么我很乐意接受对我的答案的修改,以反映避免这种竞争条件的最佳实践。
  • 它们确实会按顺序执行,但是如果有第二个查询,第一个操作读取的值取决于使用的隔离级别。有关此主题的更多信息: docs.mongodb.com/manual/core/read-isolation-consistency-recency .. 尽管如此,我认为您的回答都很好,只是想插话,以防 OP 认为并发可能是一个问题。
  • 感谢@B.Fleming 的回答,我在某种程度上做了一些改变,所以我只有一个查询要运行。感谢您展示我接受您的回答的方式。谢谢