如何使用获得 N 个结果的 Mongoose 进行查询,但结合它找到的满足特定条件的任何文档?

How to make a query using Mongoose that gets N results, but combines any documents it finds that meet certain criteria?

我在 Mongoose 中有一个 Comments 集合,还有一个 return 最近五个(任意数量)评论的查询。

每条评论都与另一个文档相关联。我想做的是查询 return 最近的 5 条评论, 评论与同一其他文档相关联

所以不是这样的列表:

results = [
    { _id: 123, associated: 12 },
    { _id: 122, associated: 8 },
    { _id: 121, associated: 12 },
    { _id: 120, associated: 12 },
    { _id: 119, associated: 17 }
]

我想要 return 这样的列表:

results = [
    { _id: 124, associated: 3 },
    { _id: 125, associated: 19 }, 
    [
        { _id: 123, associated: 12 },
        { _id: 121, associated: 12 },
        { _id: 120, associated: 12 },
    ],
    { _id: 122, associated: 8 },
    { _id: 119, associated: 17 }
]

请不要太担心数据格式:它只是一个草图,试图展示我想要的那种东西。我想要一个指定大小的结果集,但其中一些结果根据某些标准分组。

显然,执行此操作的一种方法是只进行查询、抓取和修改结果,然后递归地再次进行查询,直到结果集达到所需的长度。那种方式看起来很尴尬。有没有更好的方法来解决这个问题?我无法在 Google 搜索中以一种让我接近任何可能有洞察力的人的方式来表达它。

这是一个聚合管道查询,可以满足您的要求:

db.comments.aggregate([
    { $group: { _id: "$associated", maxID: { $max: "$_id"}, cohorts: { $push: "$$ROOT"}}},
    { $sort: { "maxID": -1 } },
    { $limit: 5 }
])

示例数据中缺少任何其他字段作为排序依据,我使用了 $_id。

如果您希望结果在结构上更接近您提供的示例结果集,您可以在末尾添加一个 $project

db.comments.aggregate([
    { $group: { _id: "$associated", maxID: { $max: "$_id"}, cohorts: { $push: "$$ROOT"}}},
    { $sort: { "maxID": -1 } },
    { $limit: 5 },
    { $project: { _id: 0, cohorts: 1 }}
])

这将只打印结果集。请注意,即使不共享关联对象的评论也将位于一个数组中。它将是一个长度为 1 的数组。

如果您担心像 Neil Lunn 所建议的那样限制分组中的结果,也许开始时 $match 是一个明智的主意。

db.comments.aggregate([
    { $match: { createDate: { $gte: new Date(new Date() - 5 * 60000) } } },
    { $group: { _id: "$associated", maxID: { $max: "$_id"}, cohorts: { $push: "$$ROOT"}}},
    { $sort: { "maxID": -1 } },
    { $limit: 5 },
    { $project: { _id: 0, cohorts: 1 }}
])

如果您有 createDate 类型字段,那将只包括最近 5 分钟内发表的评论。如果这样做,您也可以考虑使用它作为排序依据的字段而不是“_id”。如果您没有 createDate 类型字段,我不确定如何最好地限制分组的评论,因为我不知道 "current _id" 有 [=30] =].

老实说,我认为你在这里问了很多,我自己并不能真正看到实用程序,但如果我遗漏了一些有用的东西,我总是很乐意向我解释。

底线是您希望按日期来自最后五个不同用户的评论,然后对这些用户的其他评论进行某种分组。最后一部分是我认为规则有困难的地方,无论你想如何攻击它,但我会尽量保持最简短的形式。

在任何类型的单个查询中都不可能发生这种情况。但是可以做一些事情来使其成为高效的服务器响应:

var DataStore = require('nedb'),
    store = new DataStore();

async.waterfall(

    function(callback) {
        Comment.aggregate(
            [
                { "$match": { "postId": thisPostId } },
                { "$sort": { "associated": 1, "createdDate": -1 } },
                { "$group": {
                    "_id": "$associated",
                    "date": { "$first": "$createdDate" } 
                }},
                { "$sort": { "date": -1 } },
                { "$limit": 5 }
            ],
            callback);
    },

    function(docs,callback) {
        async.each(docs,function(doc,callback) {
            Comment.aggregate(
                [
                    { "$match": { "postId": thisPostId, "associated": doc._id } },
                    { "$sort": { "createdDate": -1 } },
                    { "$limit": 5 },
                    { "$group": { 
                        "_id": "$associated",
                        "docs": { 
                            "$push": {
                                "_id": "$_id", "createdDate": "$createdDate"
                            }
                        },
                        "firstDate": { "$first": "$createdDate" }
                    }}
                ],
                function(err,results) {
                    if (err) callback(err);
                    async.each(results,function(result,callback) {
                        store.insert( result, function(err, result) {
                            callback(err);
                        });
                    },function(err) {
                        callback(err);
                    });
                }
            );
        },
        callback);
    },

    function(err) {
        if (err) throw err;
        store.find({}).sort({ "firstDate": - 1 }).exec(function(err,docs) {
            if (err) throw err;
            console.log( JSON.stringify( docs, undefined, 4 ) );
        });
    }
);

现在我在文档和数组中添加了更多的文档属性,但是基于您的示例的简化形式会像这样出现:

results = [
    { "_id": 3,  "docs": [124] },
    { "_id": 19, "docs": [125]  }, 
    { "_id": 12, "docs": [123,121,120] },
    { "_id": 8,  "docs": [122] },
    { "_id": 17, "docs": [119] }
]

所以基本的想法是首先通过基本上砍掉最后 5 个来找到你独特的 "users" 最后评论的人。在这里不过滤某种范围会遍历整个集合以获得那些结果,所以最好以某种方式限制它,比如在最后一个小时或最后几个小时或根据需要采取合理的措施。只需将这些条件添加到 $match 以及与评论关联的当前 post。

一旦你有了这 5 个,那么你想获得这些用户的多个评论的任何可能的 "grouped" 详细信息。同样,通常建议对时间范围进行某种限制,但作为一般情况,这只是查找用户对当前 post 的最新评论并将其限制为 5.

这里的执行是并行完成的,这将使用更多资源,但考虑到只有 5 个查询 运行 还是相当有效的。与您的示例输出相反,此处的数组位于文档结果内部,它包含每个评论的原始文档 id 值以供参考。与文档相关的任何其他内容以及所需内容(即评论的内容)都将被推送到数组中。

这里的另一个小技巧是使用 nedb as a means for storing the output of each query in an "in memory" collection. This need only really be a standard hash data structure, but nedb 为您提供了一种方法,同时保持您可能习惯的 MongoDB 语句形式。

获得所有结果后,您只需 return 将它们作为输出,并如图所示进行排序以保留最后评论者的顺序。实际的评论被分组在每一项的数组中,你可以遍历它来输出你喜欢的。

这里的底线是您要求 "top N results problem" 的复合版本,这是 MongoDB 经常被问到的问题。我之前写过解决这个问题的方法,以展示在单个聚合管道阶段如何实现这一点,但除了相对较小的结果集之外,它真的不实用。

如果您真的想参与其中,那么您可以查看 Mongodb aggregation $group, restrict length of array 以获得更详细的示例之一。但是为了我的钱,我每天都会 运行 进行并行查询。 Node.js 有合适的环境来支持他们,否则你会很疯狂。