MongoDB 每组 select 具有最大值的记录

MongoDB For each group select the records with the max value

在 MongoDB 中,我试图将集合过滤为仅包含各自组中包含最新日期的文档。

在传统的 SQL 中,我会这样做:

Select *
From table a
Join (Select my_group, max(date) as max_date
      From table group by my_group) b 
      ON a.my_group = b.my_group AND
         a.date = b.max_date

带有以下示例集合:

[
  {
    "_id": "123",
    "item1": "group 1",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-01"
  },
  {
    "_id": "234",
    "item1": "group 1",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-02"
  },
  {
    "_id": "345",
    "item1": "group 1",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-02"
  },
  {
    "_id": "789",
    "item1": "group 2",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-01"
  },
  {
    "_id": "678",
    "item1": "group 2",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-02"
  },
  {
    "_id": "456",
    "item1": "group 2",
    "item2": "abc",
    "item3": "abc",
    "date": "2022-01-02"
  }
]

预期输出为:

[
    {
        "_id": "234",
        "date": "2022-01-02",
        "item1": "group 1",
        "item2": "abc",
        "item3": "abc"
    },
    {
        "_id": "345",
        "date": "2022-01-02",
        "item1": "group 1",
        "item2": "abc",
        "item3": "abc"
    },
    {
        "_id": "678",
        "date": "2022-01-02",
        "item1": "group 2",
        "item2": "abc",
        "item3": "abc"
    },
    {
        "_id": "456",
        "date": "2022-01-02",
        "item1": "group 2",
        "item2": "abc",
        "item3": "abc"
    }
]

我目前最好的尝试是:

db.collection.aggregate([
  {
    $group: {
      "_id": "$item1",
      "max_date": {
        $max: "$date"
      },
      "records": {
        $push: "$$ROOT"
      }
    }
  },
  {
    "$project": {
      items: {
        "$filter": {
          "input": "$records",
          "as": "records",
          "cond": {
            $eq: [
              "$$records.date",
              "$max_date"
            ]
          }
        }
      }
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        results: "$items"
      }
    }
  }
])

不幸的是,returns 结果按组划分。我尝试了其他帖子建议的一些替代方案并遇到了类似的问题,例如:

这是一个包含查询和示例数据的 playground example

你离答案很近了。

最后两个阶段:

  1. $unwind - 将items数组字段解构为多个文档。

  2. $replaceWith - 用 items 文档替换输出文档。

db.collection.aggregate([
  {
    $group: {
      "_id": "$item1",
      "max_date": {
        $max: "$date"
      },
      "records": {
        $push: "$$ROOT"
      }
    }
  },
  {
    "$project": {
      items: {
        "$filter": {
          "input": "$records",
          "as": "records",
          "cond": {
            $eq: [
              "$$records.date",
              "$max_date"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$items"
  },
  {
    $replaceWith: "$items"
  }
])

Sample Mongo Playground


奖金

虽然上面的查询更好,但也想分享类似于 SQL 实现的 MongoDB 查询。

  1. $group - 按 item1 分组并获得 date.
  2. 的最大值
  3. $lookup - 自己加入 item1date 的集合。和returnsitems数组字段。
  4. $match - 使用 items 而非空数组过滤文档。
  5. $unwind - 将 items 数组解构为多个文档。
  6. $replaceWith - 将输出文档替换为 items 文档。
db.collection.aggregate([
  {
    $group: {
      "_id": "$item1",
      "max_date": {
        $max: "$date"
      }
    }
  },
  {
    $lookup: {
      from: "collection",
      let: {
        item1: "$_id",
        max_date: "$max_date"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$item1",
                    "$$item1"
                  ]
                },
                {
                  $eq: [
                    "$date",
                    "$$max_date"
                  ]
                }
              ]
            }
          }
        }
      ],
      as: "items"
    }
  },
  {
    $match: {
      items: {
        $ne: []
      }
    }
  },
  {
    $unwind: "$items"
  },
  {
    $replaceWith: "$items"
  }
])

Sample Mongo Playground (Bonus)