【问题标题】:Get last documents with a distinct criteria获取具有不同标准的最新文档
【发布时间】:2013-10-01 11:04:39
【问题描述】:

情况

我在想出一个很好的方法来执行某个 MongoDb 查询时遇到了麻烦。首先,这是我想要做的查询。假设一个简单的数据库通过电子刷卡记录进入和退出事件(可能还有其他动作,没关系)。所以有一个名为 swipelog 的集合,其中包含如下所示的简单文档:

{
    _id: ObjectId("524ab4790a4c0e402200052c")
    name: "John Doe", 
    action: "entry",
    timestamp: ISODate("2013-10-01T1:32:12.112Z") 
}

现在我想列出姓名及其最后输入时间(以及我可能想要的任何其他字段,但下面的示例仅使用这两个字段)。

当前解决方案

这是我现在所拥有的,作为 MongoDb JavaScript 控制台的“单线”:

db.swipelog.distinct('name')
           .forEach( function(name) {

    db.swipelog.find( { name: name, action:"entry" } )
               .sort( { $natural:-1 } )
               .limit(1)
               .forEach( function(entry) {

        printjson( [ entry.name, entry.timestamp ] )
    }) 
})

打印如下内容:

[ "John Doe", ISODate("2013-10-01T1:32:12.112Z")]
[ "Jane Deo", ISODate("2013-10-01T1:36:12.112Z")]
...

问题

我认为上面有明显的缩放问题。如果有一百个名字,那么将对数据库进行 1+100 次查询。那么获得“每个不同name的最后一个timestamp”的好/正确方法是什么?更改数据库结构或添加一些集合是可以的,如果这样做更容易的话。

【问题讨论】:

  • ...在有人指出之前,是的,name 是一种糟糕的识别人的方法,但这只是示例代码。如果这让您感到困扰,请假设具有相同姓名的新人必须在获得卡片之前更改他们的姓名;)

标签: javascript mongodb mongodb-query


【解决方案1】:

您可以使用聚合框架来实现:

 db.collection.aggregate(
          [
             {$match:
                  {action:'entry'}
             },
             {$group:
                  {_id:'$name',
                   first:
                         {$max:'$timestamp'}
                  }
             }
          ])

如果您可能在结果中包含其他字段,您可以使用$first 运算符

 db.collection.aggregate(
          [
             {$match:
                  {action:'entry'}
             },
             {$sort:
                  {name:1, timestamp:-1}
             },
             {$group:
                  {_id:'$name',
                   timestamp: {$first:'$timestamp'},
                   otherField: {$first:'$otherField'},
                  }
             }
          ])

【讨论】:

  • 似乎有效!但是如何从 same 文档中添加更多字段,$timestamp 来自哪里?我的 MongoDb 新手大脑能想到的唯一方法是获取 $_id 这样的所需文档,然后进行第二次查询以获取这组文档(当然,这比我的问题要好得多) .
  • 我更新了答案,为您的问题提供了可能的解决方案,以防您可能包含其他字段。
  • 啊,这样!第二个版本正是我所追求的,我学到了很多关于如何使用aggregate的知识。谢谢!
  • 关于第二段代码的另一个问题:为什么要同时按nametimestamp 排序?就像,它只是为了对名称进行排序以防我想要对它们进行排序,还是它提高了性能,或者它实际上是获得正确结果所必需的?
  • 这是为了得到正确的结果,据我所知,可能足以对日期进行排序(可能是组做剩下的),我没有测试这部分。
【解决方案2】:

这个答案应该是对上述 attish 答案的评论,但我这里没有足够的代表发表评论

请记住聚合框架cannot return more than 16MB of data。如果您有大量用户,您的生产系统可能会遇到此限制。

MongoDB 2.6 为聚合框架添加了新功能来处理这个问题:

  • db.collection.aggregateCursor()(临时名称)与db.collection.aggregate() 相同,只是它返回的是游标而不是文档。这避免了 16MB 的限制
  • $out 是一个新的管道阶段,它将管道的输出定向到集合。这允许您运行聚合作业
  • $sort 已被改进以删除其 RAM limitations 并提高速度

如果查询性能比数据年龄更重要,您可以安排一个常规的aggregate 命令,将其结果存储在集合中,如db.last_swipe,然后让您的应用程序简单地为相关用户查询db.last_swipe

结论:我同意 attish 的方法是正确的。但是,在当前的 MongoDB 版本上扩展它可能会遇到问题,应该考虑使用 Mongo 2.6。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-27
    • 2021-03-19
    • 2021-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多