如何用MongoDB单条查询实现指定Stripe用户订阅数组的对象更新或新增
解决方案
完全可以通过一条MongoDB语句实现需求,无需分两次查询执行判断,以下是适配不同版本的实现方案:
方案1:MongoDB 4.2+ 聚合管道更新(推荐)
4.2及以上版本支持使用聚合管道作为更新逻辑,可直接在单条更新语句中实现「存在则更新、不存在则新增」的逻辑,全程原子操作无并发问题:
db.users.updateOne( // 匹配stripe_id对应的用户文档 { 'custom_data.stripe_id': stripe_id }, // 聚合管道实现条件更新 [ { $set: { 'custom_data.subscriptions': { $cond: { // 先判断订阅是否已存在于数组中 if: { $in: [subscription_id, '$custom_data.subscriptions.subscription_id'] }, // 存在则遍历数组,替换匹配的订阅对象 then: { $map: { input: '$custom_data.subscriptions', in: { $cond: { if: { $eq: ['$$this.subscription_id', subscription_id] }, then: subscriptionData, else: '$$this' } } } }, // 不存在则在数组末尾追加新订阅 else: { $concatArrays: ['$custom_data.subscriptions', [subscriptionData]] } } } } } ] )
方案2:低版本兼容方案(4.2以下)
如果使用的MongoDB版本低于4.2,可使用bulkWrite批量操作实现,两个更新操作的过滤条件互斥,同一时间只会有一个操作生效,同样是单请求完成:
db.users.bulkWrite([ // 操作1:尝试更新已存在的订阅 { updateOne: { filter: { 'custom_data.stripe_id': stripe_id, 'custom_data.subscriptions.subscription_id': subscription_id }, update: { $set: { 'custom_data.subscriptions.$': subscriptionData } } } }, // 操作2:如果没有匹配到已存在的订阅,就追加新订阅 { updateOne: { filter: { 'custom_data.stripe_id': stripe_id, 'custom_data.subscriptions.subscription_id': { $ne: subscription_id } }, update: { $push: { 'custom_data.subscriptions': subscriptionData } } } } ])
原写法问题说明
- 第一种写法的过滤条件直接匹配顶层的
subscription_id字段,而该字段实际存储在custom_data.subscriptions的子元素中,无法匹配到对应用户,因此会生成错误的独立文档。 - 第二种写法直接用
$set覆盖整个custom_data.subscriptions字段,没有定位到数组中匹配的子元素,要么会把数组替换为单个对象,要么会生成重复数据。
内容的提问来源于stack exchange,提问作者Stf_F
相关产品推荐
相关产品推荐

