MongoDB: 通过 date/time 获取每个 id 的最新完整文档

MongoDB: get latest full document for each id by date/time

我需要获取基于 data/time 的 id 数组中的最新文档。我有以下查询执行此操作,但它仅 returns _idacquiredTime 字段。我怎样才能得到 return 包含所有字段的完整文档?

db.trip.aggregate([
   { $match: { tripId: { $in: ["trip01", "trip02" ]}} },
   { $sort: { acquiredTime: -1} },
   { $group: { _id: "$tripId" , acquiredTime: { $first: "$acquiredTime" }}} 
])

集合看起来像:

[{
   "tripId": "trip01",
   "acquiredTime": 1000,
   "name": "abc",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 1000,
   "name": "xyz",
   "value": "xyz"
},{
   "tripId": "trip01",
   "acquiredTime": 2000,
   "name": "def",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 2000,
   "name": "ghi",
   "value": "xyz"
}]

此刻我得到:

[{
   "tripId": "trip01",
   "acquiredTime": 2000
},{
   "tripId": "trip02",
   "acquiredTime": 2000
}]

我需要得到:

[{
   "tripId": "trip01",
   "acquiredTime": 2000,
   "name": "def",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 2000,
   "name": "ghi",
   "value": "xyz"
}]

您的方法是正确的方法,但问题是 $group and $project 只是行不通,需要您在结果中命名您想要的所有字段。

如果您不介意结构看起来有点不同,那么您始终可以在 MongoDB 2.6 及更高版本中使用 $$ROOT

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": { "acquiredTime": -1} },
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}} 
])

所以整个文档都在那里,但都作为子文档包含在结果中 "doc"。

对于其他任何内容或更漂亮的内容,您将必须指定所需的每个字段。它只是一个数据结构,因此您始终可以通过代码生成它。

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": { "acquiredTime": -1} },
   { "$group": { 
       "_id": "$tripId" , 
       "acquiredTime": { "$first": "$acquiredTime" },
       "name": { "$first": "$name" },
       "value": { "$first": "$value" }
   }}
])

根据我的理解,当要返回大量唯一文档时,上述解决方案会遇到性能和 RAM 问题,因为 $match 的输出排序在内存,不管你有什么索引。

参考:https://docs.mongodb.com/manual/tutorial/sort-results-with-indexes/

要最大化性能并最小化 RAM 使用:

  • 创建唯一索引[(tripId, 1), (acquiredTime, -1)]
  • 有排序正好沿着索引

这当然会花费您一个索引,这会减慢插入速度 - 天下没有免费的饭:)

此外,将原始文档移动到子文档的美观问题可以使用 $replaceRoot 轻松解决,无需明确列出文档键。

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": SON([("tripId", 1), ("acquiredTime", -1)],
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}},
   { "$replaceRoot": { "newRoot": "$doc"}} 
])

最后,值得注意的是,如果 acquiredTime 只是您的服务器时间,您可以去掉它,因为 _id 已经嵌入了创建时间戳。因此唯一索引将继续 [(tripId, 1), (_id, -1)],查询变为:

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": SON([("tripId", 1), ("_id", -1)],
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}},
   { "$replaceRoot": { "newRoot": "$doc"}} 
])

这也更好,因为 MongoDB 中的日期对象具有 1 毫秒的分辨率,这取决于插入的频率 - 可能导致极难重现竞争条件,而自动生成的 _id保证严格递增。