【问题标题】:Mongodb - to group documents and get document from each group with max value of a fieldMongodb - 对文档进行分组并从每个组中获取具有字段最大值的文档
【发布时间】:2019-10-23 15:32:45
【问题描述】:

我有一个 Mongo 集合,其中的文档永远不会更新,而是读取最新的文档,并插入一个新文档,其中一些字段已更新。这些通用文档共享一个标识符document_identifier,将它们与其他文档区分开来。

我想执行一个执行以下操作的查询: 获取所有customer_id 为X 的文档,将它们按document_identifier 分组,然后从每个组中取出最大updated_at 时间戳的文档。它应该完整地返回文档(它们的所有属性)。

示例数据集:

{
    document_identifier: "abc",
    updated_at: 1000,
    customer_id: "123",
    ...  
},

{
    document_identifier: "def",
    updated_at: 1001,
    customer_id: "123",
    ...
},

{
    document_identifier: "abc",
    updated_at: 1002,
    customer_id: "123",
    ...
},

{
    document_identifier: "def",
    updated_at: 10003,
    customer_id: "123",
    ...
},

{
    document_identifier: "xyz",
    updated_at: 1004,
    customer_id: "999",
    ...
},

{
    document_identifier: "abc",
    updated_at: 1005,
    customer_id: "123",
    ...
},

{
    document_identifier: "def",
    updated_at: 1006,
    customer_id: "123",
    ...
},

在上面的示例中,如果我想查询“123”的customer_id,结果将是:

{
    document_identifier: "abc",
    updated_at: 1005,
    customer_id: "123",
    ...
},

{
    document_identifier: "def",
    updated_at: 1006,
    customer_id: "123",
    ...
},

我被引导到 Mongo 聚合框架,但似乎无法理解。

非常感谢任何帮助。

编辑:这是我现在拥有的,它似乎正在工作,但我不确定它是否是最佳的:

db.my_colleciton.aggregate([
    {
       $match: {customer_id: <value to query on>}
    },

    {
        $sort: {updated_at: -1}
    },

    {
        $group: {
            _id: "$document_identifier",
            my_doc: {$first: "$$ROOT"}
        }
    },

    {
        "$replaceRoot": {newRoot: "$my_doc"}
    }
])

【问题讨论】:

  • 我认为您的结果对于客户 123 文档 ID“def”不准确,我认为这应该显示 updated_at 值 10003 而不是 1006,因为 10003 大于 1006。
  • @barrypicker 我做了一些修改。我遗漏了一些信息,我修正了一个错字。
  • 即便如此,您的预期结果还是如我所料。也许我误解了你的意图?
  • 小心假设 $group 之前的 $sort 将提供预期的结果。 MongoDB 文档清楚地表明 $group 不会对其输出文档进行排序,因此 $first 现在是可疑的。见docs.mongodb.com/manual/reference/operator/aggregation/group
  • 实际上,再次查看文档,Mongo 声明 $first “从每个组的第一个文档中返回一个值。仅当文档处于定义的顺序时才定义顺序。”我认为他们指的是 $group 之前的 $sort。如果是这样,您的查询是安全的。我希望文档更清楚。

标签: mongodb aggregation-framework


【解决方案1】:

首先降序updated_at然后$groupdocument_identifier排序,并通过$first选择该特定组的第一个文档

并通过 $first 的相同思想保留字段。

查询: Demo Link

db.collection.aggregate([
    { $sort: { updated_at: -1 } },
    {
      $group: {
        _id: "$document_identifier",
        document_identifier: { $first: "$document_identifier" },
        updated_at: { $first: "$updated_at" },
        customer_id: { $first: "$customer_id" }
      }
    }
  ]).pretty();

结果:

{
    "_id" : "abc",
    "document_identifier" : "abc",
    "updated_at" : 1005,
    "customer_id" : "123"
},
{
    "_id" : "xyz",
    "document_identifier" : "xyz",
    "updated_at" : 1004,
    "customer_id" : "999"
},
{
    "_id" : "def",
    "document_identifier" : "def",
    "updated_at" : 10003,
    "customer_id" : "123"
}

【讨论】:

  • OP 的字段多于问题中表示的字段,并希望在输出时保留这些字段。
  • 我看不出获得像这样的所有字段(30 个或更多)或您之前使用投影显示的方式有什么问题。虽然$replaceRoot 可以完成这项工作,但如果管道没有大量的转换和替换,我发现它很密集。
  • 我同意你的说法。服务器必须做的工作越少,输出的速度就越快。一项挑战是为动态或不断变化的文档结构提供输出。如果结构不同或未来有可能发生变化,则预测阶段必须处理。我不确定这是否是 OP 关心的问题。
  • 嗯,这是一个非常好的观点,在回复之前的评论之前我没有想到。 +1。
【解决方案2】:

所以如果我理解正确,我认为这个查询可能会有所帮助......

db.records.aggregate(
[
    { $group: {
        _id: {customer_id: "$customer_id", document_identifier: "$document_identifier"},
        max_updated_at: { $max:  "$updated_at" }
    }}
])

这个想法是在两个字段上进行分组,customer_iddocument_identifier。对于该组合​​,请显示最大 updated_at,它应该是一个滚动整数。

对于您提供的数据集,我的结果显示...

{ "_id" : { "customer_id" : "123", "document_identifier" : "def" }, "max_updated_at" : 10003 }
{ "_id" : { "customer_id" : "999", "document_identifier" : "xyz" }, "max_updated_at" : 1004 }
{ "_id" : { "customer_id" : "123", "document_identifier" : "abc" }, "max_updated_at" : 1005 }

输出的格式与您的示例不同。可以吗,还是您需要输出格式来匹配您的示例?

编辑: 因此,OP 要求输出的格式与问题中描述的预期格式相匹配。废话不多说……

db.records.aggregate(
[
    { $group: {
        _id: {customer_id: "$customer_id", document_identifier: "$document_identifier"},
        max_updated_at: { $max:  "$updated_at" }
    }},
    { $project: {
        _id: 0,
        document_identifier: "$_id.document_identifier",
        updated_at: "$max_updated_at",
        customer_id: "$_id.customer_id"

    }}
]
)

现在输出看起来像:

{ "document_identifier" : "def", "updated_at" : 10003, "customer_id" : "123" }
{ "document_identifier" : "xyz", "updated_at" : 1004, "customer_id" : "999" }
{ "document_identifier" : "abc", "updated_at" : 1005, "customer_id" : "123" }

编辑编号 2:

好的,所以 OP 的字段比问题中表示的要多得多,并且希望查看匹配文档的所有字段。这是到目前为止的查询...

db.records.aggregate(
[
    { $match: { customer_id: "123" }},
    { $group: {
        _id: {customer_id: "$customer_id", document_identifier: "$document_identifier"},
        max_updated_at: { $max:  "$updated_at" }
    }},
    { $lookup: {
        from: "records",
        let: {
          customer_id: "$_id.customer_id",
          document_identifier: "$_id.document_identifier",
          max_updated_at: "$max_updated_at"
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  { $eq: [ "$customer_id", "$$customer_id"] },
                  { $eq: [ "$document_identifier", "$$document_identifier"] },
                  { $eq: [ "$updated_at", "$$max_updated_at"] }
                ]
              }
            }
          }
        ],
        as: "result"
    }},
    { $unwind: "$result" } ,
    { $replaceRoot: { newRoot: "$result" } }
]
)

这现在首先匹配客户 ID。然后它使用 $lookup 进行自连接,然后使用 $replaceRoot 仅显示原始文档。无论存在多少字段,这都会保留原始文档格式。

输出:

{ "_id" : ObjectId("5db07a5d3cf0c979dd020f85"), "document_identifier" : "def", "updated_at" : 10003, "customer_id" : "123" }
{ "_id" : ObjectId("5db07a5d3cf0c979dd020f87"), "document_identifier" : "abc", "updated_at" : 1005, "customer_id" : "123" }

【讨论】:

  • 我想要在每种情况下的整个文档,我不只是想要一些字段。另外我刚刚尝试了查询,它与预期的行为不太匹配。
  • @lorenzo - 输出已重新格式化。请查看。
  • 实际上,这些文档有大约 30 个属性,并且必须指出我想要每个属性似乎并不实际。
  • 嗯,我的例子是基于我提供的信息。如果您需要整个文档,您可以使用 $group 来识别记录,然后使用 $lookup 执行自联接。我将模拟一些东西并编辑我的答案......
  • 我认为需要有一个 $match 管道来匹配我们正在查询的customer_id
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-03
  • 1970-01-01
相关资源
最近更新 更多