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

Posted

技术标签:

【中文标题】如何使用 Mongoose 进行查询,获得 N 个结果,但结合它找到的任何满足特定条件的文档?【英文标题】:How to make a query using Mongoose that gets N results, but combines any documents it finds that meet certain criteria? 【发布时间】:2015-03-08 12:16:59 【问题描述】:

我在 Mongoose 中有一个 Comments 集合,以及一个返回最近五个(任意数量)Comments 的查询。

每个评论都与另一个文档相关联。我想做的是进行一个查询,返回最近的 5 个 cmets,其中 cmets 与相同的其他文档相关联

所以不是这样的列表:

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

我想返回一个这样的列表:

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 搜索中以一种让我接近任何可能有洞察力的人的方式来表达它。

【问题讨论】:

我在这里看到的问题是这样的。对于您在此处按“12”进行的分组,所画的线在哪里,上面写着“在此组中停止列出结果”。在我看来,从上到下至少有一个条目在“最近的 5 个”之内,但是你正在分组的那些呢?需要有一个约束,例如没有帖子可以比最旧的帖子更早。否则,这里的危险是在任何分组中获取大量帖子。那么什么是规则呢?也许是这个建议或其他什么? 我宁愿对分组项目没有限制:将它们想象成一个折叠的下拉列表,其中可以包含任意数量的项目而不会导致 UI 问题。 尝试在这里接受教育。想想看。如果您按“关联用户”排序,然后按日期降序排序,那么在移动到下一个用户之前您在哪里停止。如果您按日期排序,然后按“关联用户”排序,那么您又会在哪里停下来?在收集了多少个可能的分组之后。某处必须有一条线。人们制造了很多 cmets。 好的:我不反对某种限制。鉴于此,我该如何处理? 【参考方案1】:

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

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 
])

这将只打印结果集。请注意,即使不共享关联对象的 cmets 也将位于数组中。它将是一个长度为 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 分钟内创建的 cmets。如果这样做,您也可以考虑使用它作为排序依据而不是“_id”。如果您没有 createDate 类型字段,我不确定如何最好地限制分组的 cmets,因为我不知道“当前 _id”以存在“当前时间”的方式。

【讨论】:

这是我需要的。当我昨晚扫描文档时,我的 Mongo[oose] fu 太弱了,无法识别聚合可以为我提供所需的东西。感谢您的帮助!【参考方案2】:

老实说,我认为您在这里问了很多问题,但我自己并不能真正看到该实用程序,但如果我错过了一些有用的东西,我总是很高兴向我解释。

底线是您希望按日期从最后五个不同用户中获取 cmets,然后按这些用户对其他 cmets 进行某种分组。最后一部分是无论你想如何攻击,我都认为规则存在困难,但我会尽量保持最简短的形式。

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

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 个来发表评论。这里不过滤某种范围,会遍历整个集合以获得这些结果,所以最好以某种方式限制这一点,例如在最后一个小时或最后几个小时内或根据需要进行合理的操作。只需将这些条件与与 cmets 关联的当前帖子一起添加到 $match

一旦您拥有了这 5 个,那么您希望获得这些用户为多个 cmet 提供的任何可能的“分组”详细信息。同样,通常建议对时间范围进行某种限制,但作为一般情况,这只是在当前帖子中查找用户最近的 cmets,并将其限制为 5。

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

这里的另一个小技巧是使用nedb 作为将每个查询的输出存储在“内存中”集合中的一种方式。这实际上只需要一个标准的哈希数据结构,但nedb 为您提供了一种方法,同时保持您可能习惯的 MongoDB 语句形式。

获得所有结果后,您只需将它们作为输出返回,并按所示排序以保留最后评论者的顺序。实际的 cmets 在数组中为每个项目分组,您可以遍历它以输出您喜欢的方式。

这里的底线是,您要求的是“前 N 个结果问题”的复合版本,这是 MongoDB 经常提出的问题。我之前已经写过解决这个问题的方法,以展示如何在单个聚合管道阶段实现它,但对于相对较小的结果集而言,它确实不实用。

如果您真的想加入疯狂,那么您可以查看Mongodb aggregation $group, restrict length of array 以获得更详细的示例之一。但是为了我的钱,我会在任何一天运行并行查询。 Node.js 有合适的环境来支持它们,否则你会疯掉的。

【讨论】:

我会退后一步:我想在我的应用程序中有一个小部件来显示最近的 cmets。但我不希望该列表被单个用户在单个游戏中创建的最后 30 个 cmets 所覆盖。我希望它更能代表不同用户最近的一般活动。无论如何,我非常感谢您抽出时间尝试更好地解释情况并给我一些背景信息。谢谢! @Nate 所以在我看来,你仍然必须采用这种方法才能真正做到这一点。无论是将单个用户制作的多个 cmets 推送到一个数组中,还是只保留他们的第一条或最后一条评论。但是要获得“外部” 30,您首先需要一个不同的列表,然后查询这些不同用户的详细信息。否则只是猜测。你也许可以接受猜测,但这不是你问的问题。

以上是关于如何使用 Mongoose 进行查询,获得 N 个结果,但结合它找到的任何满足特定条件的文档?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Mongoose 查询对嵌入文档数组进行排序?

设置 Mongoose 模式进行身份验证后,如何使用 Mongodb 查询数据库?

如何使用 Mongoose 进行查询,然后拿该文档做其他事情?

Mongoose 挂起第二个查询

在使用 mongoose 完成两个异步查询后进行回调

如何在 Node.js 中通过 Mongoose 使用 dot(.) 进行查询以及如何添加空数组