MongoDB:聚合框架:根据分组ID获取最新日期的文档

MongoDB:聚合框架:根据分组ID获取最新日期的文档,mongodb,aggregation-framework,Mongodb,Aggregation Framework,我想获取每个站点的最后一个文档以及所有其他字段: { "_id" : ObjectId("535f5d074f075c37fff4cc74"), "station" : "OR", "t" : 86, "dt" : ISODate("2014-04-29T08:02:57.165Z") } { "_id" : ObjectId("535f5d114f075c37fff4cc75"), "station

我想获取每个站点的最后一个文档以及所有其他字段:

{
        "_id" : ObjectId("535f5d074f075c37fff4cc74"),
        "station" : "OR",
        "t" : 86,
        "dt" : ISODate("2014-04-29T08:02:57.165Z")
}
{
        "_id" : ObjectId("535f5d114f075c37fff4cc75"),
        "station" : "OR",
        "t" : 82,
        "dt" : ISODate("2014-04-29T08:02:57.165Z")
}
{
        "_id" : ObjectId("535f5d364f075c37fff4cc76"),
        "station" : "WA",
        "t" : 79,
        "dt" : ISODate("2014-04-29T08:02:57.165Z")
}
我需要每个站点的最新dt的t和station。 使用聚合框架:

db.temperature.aggregate([{$sort:{"dt":1}},{$group:{"_id":"$station", result:{$last:"$dt"}, t:{$last:"$t"}}}])
返回

{
        "result" : [
                {
                        "_id" : "WA",
                        "result" : ISODate("2014-04-29T08:02:57.165Z"),
                        "t" : 79
                },
                {
                        "_id" : "OR",
                        "result" : ISODate("2014-04-29T08:02:57.165Z"),
                        "t" : 82
                }
        ],
        "ok" : 1
}
这是最有效的方法吗


谢谢

索引是您真正需要的:

db.temperature.ensureIndex({ 'station': 1, 'dt': 1 })
for s in db.temperature.distinct('station'):
    db.temperature.find({ station: s }).sort({ dt : -1 }).limit(1)
当然,使用对您的语言实际有效的语法

编辑:你是对的,像这样的循环会导致每个站点的往返,这对一些站点来说是很好的,而对1000站点来说不是很好。不过,您仍然希望station+dt上的复合索引,并利用降序排序:

db.temperature.aggregate([
    { $sort: { station: 1, dt: -1 } },
    { $group: { _id: "$station", result: {$first:"$dt"}, t: {$first:"$t"} } }
])

就您发布的聚合查询而言,我会确保您在dt上有一个索引:

db.temperature.ensureIndex({'dt': 1 })
这将确保聚合管道开头的$sort尽可能高效

与循环中的查询相比,这是否是获取此数据的最有效方法,很可能取决于您拥有多少数据点。一开始,有了“数千个站点”和数十万个数据点,我认为聚合方法会更快


但是,随着您添加越来越多的数据,一个问题是聚合查询将继续涉及所有文档。当您扩展到数百万或更多文档时,这将变得越来越昂贵。这种情况下的一种方法是在$sort之后添加$limit,以限制正在考虑的文档总数。这有点粗糙和不精确,但它有助于限制需要访问的文档总数。

要直接回答您的问题,是的,这是最有效的方法。但我认为我们需要澄清为什么会这样

正如在备选方案中所建议的那样,人们看到的一件事是在传递到
$group
阶段之前对结果进行“排序”,他们看到的是“timestamp”值,因此您需要确保所有内容都是按“timestamp”顺序排列的,因此形成:

db.temperature.aggregate([
{“$sort”:{“station”:1,“dt”:-1},
{“$组”:{
“_id”:“$station”,
“结果”:{“$first”:“$dt”},“t”:{“$first”:“$t”}
}}
])
如前所述,为了提高排序效率,您当然需要一个索引来反映这一点:

然而,这才是真正的问题所在。其他人(如果不是你自己)似乎忽略了的是,所有这些数据很可能已经按时间顺序插入了,因为每个读数都记录为添加的数据

因此,
\u id
字段(带有默认的
ObjectId
)已经是“timestamp”顺序,因为它本身实际上包含一个时间值,这使得该语句成为可能:

db.temperature.aggregate([
{“$组”:{
“_id”:“$station”,
“result”:{“$last”:“$dt”},“t”:{“$last”:“$t”}
}}
])
而且它更快。为什么?您不需要选择索引(要调用的其他代码),也不需要在文档之外“加载”索引

我们已经知道文档是有序的(通过
\u id
),因此
$last
边界是完全有效的。无论如何,您都在扫描所有内容,并且您还可以对
\u id
值进行“范围”查询,使其在两个日期之间同样有效

这里唯一要说的是,在“真实世界”使用中,在进行此类累积时,在日期范围之间进行
$match
可能更为实际,而不是在实际使用中获取“第一个”和“最后一个”
\u id
值来定义“范围”或类似的内容

那么,这方面的证据在哪里呢?很容易复制,所以我只是通过生成一些样本数据:

var站=[
“AL”、“AK”、“AZ”、“AR”、“CA”、“CO”、“CT”、“DE”、“FL”,
“GA”、“HI”、“ID”、“IL”、“IN”、“IA”、“KS”、“KY”、“LA”,
“ME”、“MD”、“MA”、“MI”、“MN”、“MS”、“MO”、“MT”、“NE”,
“NV”、“NH”、“NJ”、“NM”、“NY”、“NC”、“ND”、“OH”、“OK”,
“或”、“PA”、“RI”、“SC”、“SD”、“TN”、“TX”、“UT”、“VT”,
“VA”、“WA”、“WV”、“WI”、“WY”
];

对于(i=0;IU将用代码找到n个。我有数千个站……这就是为什么我希望使用聚合框架只需要一个请求。感谢索引建议,因此,在记录中,在这种情况下,定义这种类型实际上会运行得较慢。这里要考虑的是文档实际上已经处于插入顺序。在这种情况下,我编写了一个测试用例示例来证明为什么会这样。我可以使用_id进行排序,我认为它比IsoDate快。事实并非如此。
\u id
值已经按要求的顺序排列,并且有一个测试用例(如图所示)证明在这种情况下,定义索引和排序实际上会运行得更慢。@NeilLunn不正确,_id值没有按要求的顺序,除非您是从索引中读取它们(按_id排序时会发生这种情况).您从NeilLunn接受的答案事实上是不正确的。自然顺序不保证是插入顺序(封顶集合除外)和_id只有在所有客户端机器都是时间同步的情况下才能保证单调递增。您好,我使用的是相同的聚合管道格式,但如果字段不存在,$first或$last将从该字段的下一条记录中获取值,而其余字段值将从第一条记录中获取,我们如何格式化是否以所有值都来自同一记录的方式执行查询?