MongoDB单查询实现数组唯一对象添加、排序及切片
MongoDB数组字段去重添加、排序、切片单查询实现
需求与问题
需要通过单查询完成以下操作:
- 向MongoDB文档的数组字段添加新对象,自动忽略已存在的重复对象
- 添加完成后按对象的指定字段对数组排序
- 最后将数组切片至指定长度
此前尝试的方案均存在缺陷:
- 使用
$push配合$sort和$slice能实现排序与切片,但无法避免重复对象 - 使用
$addToSet可以去重,但MongoDB明确提示其不保证元素顺序,无法满足排序需求 - 尝试聚合管道更新时,不加排序逻辑可正常运行,但添加排序后功能失效
解决方案
利用MongoDB 5.0+支持的聚合管道更新语法,结合$setUnion、$sortArray、$slice三个操作符,可在单查询内完成所有需求:
示例代码(针对students集合)
db.students.updateOne( { _id: 5 }, [ // 1. 合并现有数组与新元素,自动去重(对象完全匹配则判定为重复) { $set: { quizzes: { $setUnion: [ "$quizzes", [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ] ] } } }, // 2. 按指定字段排序(此处按score降序) { $set: { quizzes: { $sortArray: { input: "$quizzes", sortBy: { score: -1 } } } } }, // 3. 切片至指定长度(保留前3个元素) { $set: { quizzes: { $slice: ["$quizzes", 3] } } } ] )
通用化代码(适配动态字段与参数)
db.collection(collection).updateOne( { _id: updateId }, [ // 去重合并新元素与现有数组 { $set: { [relatedRel]: { $setUnion: [ `$${relatedRel}`, [updatedDoc] ] } } }, // 按自定义规则排序(替换sortBy内的规则即可) { $set: { [relatedRel]: { $sortArray: { input: `$${relatedRel}`, sortBy: { score: -1 } // 示例:按score降序 } } } }, // 切片至指定长度 { $set: { [relatedRel]: { $slice: [`$${relatedRel}`, relatedRelation.limit] } } } ] );
关键操作符说明
$setUnion:合并两个数组并自动去重,仅当两个对象的所有键值对完全一致时才会判定为重复$sortArray:MongoDB 5.0+新增的数组排序操作符,可直接对数组按指定字段排序,保证顺序$slice:截取数组至指定长度,支持正向(保留前N个)或反向(保留后N个)截取
特殊场景适配:按指定字段去重(而非整个对象)
如果需要按对象的某个字段(比如wk)判定重复,而非整个对象完全匹配,可调整第一步的逻辑,先过滤掉现有数组中重复字段的元素,再合并新元素:
db.students.updateOne( { _id: 5 }, [ { $set: { quizzes: { $concatArrays: [ // 过滤掉现有数组中wk与新元素重复的项 { $filter: { input: "$quizzes", cond: { $not: { $in: ["$$this.wk", [5, 6, 7]] // 新元素的wk列表 } } } }, [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ] ] } } }, { $set: { quizzes: { $sortArray: { input: "$quizzes", sortBy: { score: -1 } } } } }, { $set: { quizzes: { $slice: ["$quizzes", 3] } } } ] )
内容的提问来源于stack exchange,提问作者Syd Amir
相关产品推荐
相关产品推荐

