MongoDB游标分页排序查询如何处理待排序字段为null的问题
问题原因
你的代码遗漏了name为null/不存在的文档的匹配逻辑,同时MongoDB默认升序排序时null值会排在所有字符串前,和你预期的排序顺序不符,两点共同导致了无name字段的用户无法被返回:
- MongoDB中不存在的字段和任意值做比较运算都会返回false,你原来的游标匹配
$or条件只覆盖了name存在的场景,无name的文档没有匹配项,被直接过滤 - 默认升序排序规则下,null/不存在的字段优先级高于字符串,会排在有值的name前面,和你预期的「有name的按升序排完再排无name的」顺序不符
修改方案
分两步调整即可:
- 调整排序规则,在排序前新增
$addFields阶段生成辅助排序字段,确保无name的文档排在所有有name的文档之后,和你的预期顺序对齐 - 调整游标匹配逻辑,在
startFrom的$or条件中新增无name文档的匹配规则
修改后代码
let startFrom = { $match: {}, }; if (hasLastUser) { const lastUserName = lastUser.name; const isLastUserHasName = lastUserName != null; // 判断上一页最后一条是否有name const nameCompareOp = sortOrder === SortOrder.ASCENDING ? '$gt' : '$lt'; if (isLastUserHasName) { // 上一页最后一条有name,匹配三种情况:name更大/同名id更大/无name startFrom = { $match: { $or: [ { name: { [nameCompareOp]: lastUserName }, }, { name: lastUserName, _id: { $gt: after }, }, // 新增:匹配name为null/不存在的文档 { name: null } ], }, }; } else { // 上一页最后一条已经是无name的文档,只匹配id更大的无name文档 startFrom = { $match: { name: null, _id: { $gt: after } } }; } } const query = await this.userModel.aggregate([ // Stage 1: 原有过滤逻辑 { $match: { $and: [ { groups: { $elemMatch: { group: { $in: ids }, }, }, }, { $or: [ { email: { $regex: regKeyword } }, // 如果需要无name的文档也支持空关键词搜索,这里新增 { name: null } 匹配项即可 { name: { $regex: regKeyword } }, { phone: { $regex: regKeyword } }, ], }, ], }, }, // 新增Stage:生成辅助排序字段,控制无name文档的排序位置 { $addFields: { // 有name的文档排序优先级为0,无name的为1,升序排列时0在前1在后 _nameSortPriority: { $cond: { if: { $eq: [ { $type: "$name" }, "null" ] }, then: 1, else: 0 } } } }, // Stage 2: 调整后的排序规则 { $sort: { _nameSortPriority: 1, // 先按优先级排序,有name的在前 name: sortOrder === SortOrder.ASCENDING ? 1 : -1, // 再按name本身排序 _id: 1, // 最后按id排序保证唯一性 }, }, // Stage 3: 游标过滤 startFrom, // Stage 4: 分页限制 { $limit: size, }, // 可选:移除辅助字段,不返回给前端 { $unset: "_nameSortPriority" } ]);
内容的提问来源于stack exchange,提问作者James Fu
相关产品推荐
相关产品推荐

