You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

聚合查询为何按_id排序?如何维持输入数组的ID顺序?

问题

通过一组_id数组检索文档时,返回结果总是按_id字母序排序,即使在调用时传入sort: undefined,也无法维持输入数组的原始顺序。

相关代码如下:

聚合查询函数

export async function getApprovedPopulatedResources(match?: mongoose.AnyObject, sort?: Record<string, 1 | -1>, page?: number, pageSize?: number) {

    let resourcesFilter = [];

    if (sort) resourcesFilter.push({ $sort: sort })

    if (page && pageSize) {
        resourcesFilter.push({ $skip: (page - 1) * pageSize });
        resourcesFilter.push({ $limit: pageSize });
    }

    let aggregationResult = await Resource.aggregate()
        .match({
            approved: true,
            ...match
        })
        .lookup({
            from: "resourcevotes",
            localField: "_id",
            foreignField: "resourceId",
            as: "votes",
        })
        .facet({
            resources: resourcesFilter,
            totalCount: [{ $group: { _id: null, count: { $sum: 1 } } }]
        }).exec();

    const resourcesPopulated = await Resource.populate(aggregationResult[0].resources,
        [
            { path: "submittingUser" },
            { path: "lastUpdatingUser" },
        ]
    );

    const totalCount = aggregationResult[0].totalCount[0]?.count || 0;

    const result = {
        resources: resourcesPopulated,
        totalCount: totalCount,
        pageCount: Math.ceil(totalCount / (pageSize || totalCount)),
    };

    return result;
}

调用代码

const resourceIds = [...] // 这里的_id数组是正确的顺序

const result = await getApprovedPopulatedResources( // 返回的resources顺序错误,按_id字母序排列
    { _id: { $in: resourceIds } },
    undefined,
    page,
    pageSize
);
解决方案

MongoDB的$in操作符不会保留输入数组的顺序,默认会按照文档的存储顺序或索引顺序返回结果(此处表现为_id的字母序)。要维持输入_id数组的原始顺序,需要在聚合管道中添加基于输入数组位置的排序逻辑:

修改后的聚合函数

export async function getApprovedPopulatedResources(match?: mongoose.AnyObject, sort?: Record<string, 1 | -1>, page?: number, pageSize?: number) {
    let resourcesFilter = [];
    const idInArray = match?._id?.$in;

    // 如果传入了_id的$in数组且未指定自定义排序,添加基于索引的排序规则
    if (idInArray && Array.isArray(idInArray) && !sort) {
        resourcesFilter.unshift({ $sort: { order: 1 } });
    }

    // 初始化聚合管道
    let aggregationPipeline = Resource.aggregate()
        .match({
            approved: true,
            ...match
        });

    // 给匹配的文档添加order字段,值为该文档_id在输入数组中的索引
    if (idInArray && Array.isArray(idInArray) && !sort) {
        aggregationPipeline = aggregationPipeline.addFields({
            order: { $indexOfArray: [idInArray, "$_id"] }
        });
    }

    // 继续后续聚合步骤
    aggregationPipeline = aggregationPipeline
        .lookup({
            from: "resourcevotes",
            localField: "_id",
            foreignField: "resourceId",
            as: "votes",
        })
        .facet({
            resources: resourcesFilter,
            totalCount: [{ $group: { _id: null, count: { $sum: 1 } } }]
        });

    const aggregationResult = await aggregationPipeline.exec();

    const resourcesPopulated = await Resource.populate(aggregationResult[0].resources,
        [
            { path: "submittingUser" },
            { path: "lastUpdatingUser" },
        ]
    );

    const totalCount = aggregationResult[0].totalCount[0]?.count || 0;

    return {
        resources: resourcesPopulated,
        totalCount,
        pageCount: Math.ceil(totalCount / (pageSize || totalCount)),
    };
}

原理说明

  • $indexOfArray操作符会返回当前文档的_id在输入resourceIds数组中的位置索引(第一个元素索引为0,第二个为1,以此类推)。
  • 通过按order字段升序排序,就能让结果严格遵循输入数组的顺序返回。

注意事项

  • 如果不需要返回order字段,可以在facet之前添加$project步骤移除该字段,例如:.project({ order: 0 })。
  • 当调用时传入自定义sort参数时,会优先使用自定义排序规则,不会触发上述逻辑,保证函数的通用性。

内容的提问来源于stack exchange,提问作者Florian Walther

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:39:51