MongoDB实现对象数组顺序扣减数值并更新属性
MongoDB顺序扣减数组元素值的解决方案
给定如下结构的MongoDB文档:
{ "cantidad": 23, "historial": { "entradas": [ {"cantidad": 5}, {"cantidad":10}, {"cantidad":8} ], "salidas": [] } }
需要实现:指定一个扣减数值,按顺序从historial.entradas数组的元素中扣减——优先扣第一个元素,若元素值不够扣就置0,剩余扣减额转去扣下一个,直到扣减额用完;同时更新顶层cantidad为原值减去扣减额。
比如扣减7时,文档会变成这样:
{ "cantidad": 16, "historial": { "entradas": [ {"cantidad": 0}, {"cantidad": 8}, {"cantidad":8} ], "salidas": [] } }
扣减17时,entradas数组会变为:
"entradas": [ {"cantidad": 0}, {"cantidad": 0}, {"cantidad":6} ]
解决方案
用MongoDB 4.2及以上版本支持的聚合管道式更新就能实现这个逻辑,核心是用$reduce遍历数组并跟踪剩余扣减额的状态。
假设要扣减的数值是decrement(比如7或17),执行以下更新操作:
// 定义要扣减的数值 const decrement = 7; db.vale.updateOne( // 替换成你的文档匹配条件,比如根据_id匹配 {}, [ { $set: { // 更新顶层cantidad:直接减去扣减额 "cantidad": { $subtract: ["$cantidad", decrement] }, // 用$reduce处理entradas数组,生成包含结果数组和剩余扣减额的对象 "historial.entradas": { $reduce: { input: "$historial.entradas", initialValue: { remaining: decrement, newArray: [] }, in: { $let: { vars: { // 当前元素扣减后的剩余值:如果剩余扣减额比当前元素大,就置0,否则用当前值减剩余扣减额 currentVal: { $max: [0, { $subtract: ["$$this.cantidad", "$$value.remaining"] }] }, // 更新后的剩余扣减额:如果当前元素值比剩余扣减额大,剩余额置0,否则用剩余额减当前元素值 newRemaining: { $max: [0, { $subtract: ["$$value.remaining", "$$this.cantidad"] }] } }, in: { remaining: "$$newRemaining", newArray: { $concatArrays: ["$$value.newArray", [{ "cantidad": "$$currentVal" }]] } } } } } } } }, // 提取处理后的数组,覆盖原entradas { $set: { "historial.entradas": "$historial.entradas.newArray" } } ] );
逻辑说明
- 顶层cantidad更新:用
$subtract直接计算原值减去扣减额,一步到位。 - 数组处理:
$reduce会遍历entradas数组,初始化一个状态对象,包含remaining(还需要扣减的额度)和newArray(保存处理后的元素)。- 对每个元素,先算扣减后的值:如果剩余扣减额比当前元素大,元素置0;否则用当前元素值减去剩余扣减额。
- 再更新剩余扣减额:如果当前元素值够扣,剩余额置0;否则剩余额减去当前元素值。
- 把处理后的元素加到
newArray里,更新状态对象。
- 最后把
newArray提取出来,替换原来的entradas数组。
注意事项
- 必须使用MongoDB 4.2及以上版本,因为聚合管道式更新是这个版本新增的功能。
- 要保证扣减额
decrement不超过entradas数组所有元素的总和(也就是顶层的cantidad),否则最后一个元素会出现负数——你已经明确会保证这一点,所以没问题。
内容的提问来源于stack exchange,提问作者Adrian Gonzalez
相关产品推荐
相关产品推荐

