【问题标题】:Group Mongo documents by id and get the latest document by timestamp按 id 对 Mongo 文档进行分组,并按时间戳获取最新文档
【发布时间】:2016-08-04 20:34:40
【问题描述】:

假设我们在 mongodb 中存储了以下一组文档:

{ "fooId" : "1", "status" : "A", "timestamp" : ISODate("2016-01-01T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "1", "status" : "B", "timestamp" : ISODate("2016-01-02T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "1", "status" : "C", "timestamp" : ISODate("2016-01-03T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "2", "status" : "A", "timestamp" : ISODate("2016-01-01T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "2", "status" : "B", "timestamp" : ISODate("2016-01-02T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "3", "status" : "A", "timestamp" : ISODate("2016-01-01T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "3", "status" : "B", "timestamp" : ISODate("2016-01-02T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "3", "status" : "C", "timestamp" : ISODate("2016-01-03T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "3", "status" : "D", "timestamp" : ISODate("2016-01-04T00:00:00.000Z") "otherInfo" : "BAR", ... }

我想根据时间戳获取每个 fooId 的最新状态。因此,我的回报看起来像:

{ "fooId" : "1", "status" : "C", "timestamp" : ISODate("2016-01-03T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "2", "status" : "B", "timestamp" : ISODate("2016-01-02T00:00:00.000Z") "otherInfo" : "BAR", ... }
{ "fooId" : "3", "status" : "D", "timestamp" : ISODate("2016-01-04T00:00:00.000Z") "otherInfo" : "BAR", ... }

我一直在尝试通过使用 group 运算符的聚合来解决这个问题,但我想知道的部分是有一种简单的方法可以从聚合中获取整个文档,因此它看起来就像我使用了查找查询?似乎您必须在分组时指定所有字段,如果文档上可以包含我可能不知道的可选字段,这似乎不可扩展。我当前的查询如下所示:

db.collectionName.aggregate(
   [
     { $sort: { timestamp: 1 } },
     {
       $group:
         {
           _id: "$fooId",
           timestamp: { $last: "$timestamp" },
           status: { "$last": "$status" },
           otherInfo: { "$last": "$otherInfo" },
         }
     }
   ]
)

【问题讨论】:

  • 您已经以正确的方式进行操作了。当然,您可以使用$$ROOT 并将整个文档放在一个属性中,但这不是同一个结构,是吗?如果您非常担心“输入”每个字段,那么只需“在代码中生成最终的$group 管道语句”。这是一件非常简单的事情,所有 MongoDB 查询和聚合管道语句毕竟只是“数据结构”。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

您可以使用$$ROOT 系统变量和$last 运算符返回最后一个文档。

db.collectionName.aggregate([      
    { "$sort": { "timestamp": 1 } },     
    { "$group": { 
        "_id": "$fooId",   
        "last_doc": { "$last": "$$ROOT" } 
    }}
])

当然,这会将每个组的最后一个文档作为字段的值。

{
        "_id" : "2",
        "doc" : {
                "_id" : ObjectId("570e6df92f5bb4fcc8bb177e"),
                "fooId" : "2",
                "status" : "B",
                "timestamp" : ISODate("2016-01-02T00:00:00Z")
        }
}

如果您对该输出不满意,那么最好的办法是在使用 $push 累加器运算符返回这些文档的数组时向管道添加另一个 $group 阶段。

db.collectionName.aggregate([      
    { "$sort": { "timestamp": 1 } },     
    { "$group": { 
        "_id": "$fooId",   
        "last_doc": { "$last": "$$ROOT" } 
    }},
    { "$group": { 
        "_id": null, 
        "result": { "$push": "$last_doc" } 
    }}

])

【讨论】:

    【解决方案2】:

    如果你正在做聚合,你需要做类似于 SQL 的操作,这意味着指定每列的聚合操作,你唯一的选择是使用 $$ROOT 操作符

    db.test.aggregate(
       [
        { $sort: { timestamp: 1 } },
         {
           $group:
             {
               _id: "$fooId",
               timestamp: { $last: "$$ROOT" }
             }
         }
       ]
    );
    

    但这会稍微改变输出

    { "_id" : "1", "timestamp" : { "_id" : ObjectId("570e6be3e81c8b195818e7fa"), 
      "fooId" : "1", "status" : "A", "timestamp" :ISODate("2016-01-01T00:00:00Z"), 
      "otherInfo" : "BAR" } }
    

    如果要返回原始文档格式,之后可能需要一个 $project 阶段

    【讨论】:

    • 如果有一种方法可以在没有聚合的情况下做到这一点,我肯定会对此感兴趣。使用 $$ROOT 的输出肯定是不受欢迎的,使用 $project 仍然会保留您使用 $ROOT 的字段的名称。如果可以使输出看起来像您执行了普通查询,我真的很喜欢。
    • @Shark Aggregation 是您最好的选择。此外,正如我在回答中提到的,您可以将另一个 $group 阶段添加到管道中。
    【解决方案3】:

    虽然没有直接的方法可以带回原始文档并且我没有看到任何价值,但请尝试以下聚合查询:

    db.collection.aggregate([
       {$sort: {fooId:1, timestamp: -1}},
       {$group:{_id:"$fooId", doc:{$first:"$$ROOT"}}},
       {$project:{_id:0, doc:["$doc"]}}
    ]).forEach(function(item){
    
      printjson(item.doc[0]);
    
    });
    

    此查询将发出:

    { 
        "_id" : ObjectId("570e76d5e94e6584078f02c4"), 
        "fooId" : "2", 
        "status" : "B", 
        "timestamp" : ISODate("2016-01-02T00:00:00.000+0000"), 
        "otherInfo" : "BAR"
    }
    { 
        "_id" : ObjectId("570e76d5e94e6584078f02c8"), 
        "fooId" : "3", 
        "status" : "D", 
        "timestamp" : ISODate("2016-01-04T00:00:00.000+0000"), 
        "otherInfo" : "BAR"
    }
    { 
        "_id" : ObjectId("570e76d5e94e6584078f02c2"), 
        "fooId" : "1", 
        "status" : "C", 
        "timestamp" : ISODate("2016-01-03T00:00:00.000+0000"), 
        "otherInfo" : "BAR"
    }
    

    【讨论】:

    • 所以实际上,我正在使用 springframework for mongodb 在 java 中进行查询。截至目前,我有一些 mongotemplate find 查询将生成的文档映射回 collectionName.class 对象。我想为聚合查询做类似的事情,而不必创建另一个中间层对象,然后我必须映射到 collectionName.class。这是我对这件事很挑剔的主要原因
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    • 2018-09-21
    • 2021-05-05
    • 2020-11-24
    • 1970-01-01
    相关资源
    最近更新 更多