有没有一种方法可以投影一个范围内的最大值,然后仅在一个聚合中从这个最大值开始在新范围内查找文档?

Is there a way to project max value in a range then finding documents within a new range starting at this max value in just one aggregate?

给定 Mongo 集合中的以下数据:

    {
        _id: "1",
        dateA: ISODate("2021-12-31T00:00.000Z"),
        dateB: ISODate("2022-01-11T00:00.000Z")
    },
    {
        _id: "2",
        dateA: ISODate("2022-01-02T00:00.000Z"),
        dateB: ISODate("2022-01-08T00:00.000Z")
    },
    {
        _id: "3",
        dateA: ISODate("2022-01-03T00:00.000Z"),
        dateB: ISODate("2022-01-05T00:00.000Z")
    },
    {
        _id: "4",
        dateA: ISODate("2022-01-09T00:00.000Z"),
        dateB: null
    },
    {
        _id: "5",
        dateA: ISODate("2022-01-11T00:00.000Z"),
        dateB: ISODate("2022-01-11T00:00.000Z")
    },
    {
        _id: "6",
        dateA: ISODate("2022-01-12T00:00.000Z"),
        dateB: null
    }

并给出以下范围:

ISODate("2022-01-01T00:00.000Z") .. ISODate("2022-01-10T00:00.000Z")

我想在给定范围内找到所有带有 dateA 的值,然后我想从最大 dateB 值开始缩小范围,最后获取所有不包含的文档dateB.

简历中:

我将从范围开始

ISODate("2022-01-01T00:00.000Z") .. ISODate("2022-01-10T00:00.000Z")

然后换范围

ISODate("2022-01-08T00:00.000Z") .. ISODate("2022-01-10T00:00.000Z")

然后用

dateB: null

最后,结果将是

的文档
_id: "4"

有没有办法在一个聚合中找到带有 _id: "4" 的文档?

我知道如何使用 2 个查询以编程方式执行此操作,但主要目标是只对数据库发出一个请求。

您可以使用$max先找到maxDateB。然后执行 self $lookup 以应用 $match 并找到 doc _id: "4".

db.collection.aggregate([
  {
    $match: {
      dateA: {
        $gte: ISODate("2022-01-01"),
        $lt: ISODate("2022-01-10")
      }
    }
  },
  {
    "$group": {
      "_id": null,
      "maxDateB": {
        "$max": "$dateB"
      }
    }
  },
  {
    "$lookup": {
      "from": "collection",
      "let": {
        start: "$maxDateB",
        end: ISODate("2022-01-10")
      },
      "pipeline": [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $gte: [
                    "$dateA",
                    "$$start"
                  ]
                },
                {
                  $lt: [
                    "$dateA",
                    "$$end"
                  ]
                },
                {
                  $eq: [
                    "$dateB",
                    null
                  ]
                }
              ]
            }
          }
        }
      ],
      "as": "result"
    }
  },
  {
    "$unwind": "$result"
  },
  {
    "$replaceRoot": {
      "newRoot": "$result"
    }
  }
])

这是您

Mongo Playground

假设匹配的初始 dateA 范围不是很大,这里是利用 $push$filter 并避免命中 $lookup 阶段的替代方法:

db.foo.aggregate([
    {$match: {dateA: {$gte: new ISODate("2022-01-01"), $lt: new ISODate("2022-01-10")} }},

    // Kill 2 birds with one stone here.  Get the max dateB AND prep              
    // an array to filter later.  The items array will be as large                
    // as the match above but the output of this stage is a single doc:           
    {$group: {_id: null,
              maxDateB: {$max: "$dateB" },
              items: {$push: "$$ROOT"}
             }},

    {$project: {X: {$filter: {
        input: "$items",
        cond: {$and: [
                // Each element of 'items' is passed as $$this so use
                // dot notation to get at individual fields.  Note that
                // all other peer fields to 'items' like 'maxDateB' are
                // in scope here and addressable using '$':
                {$gt: [ "$$this.dateA", "$maxDateB"]},
                {$eq: [ "$$this.dateB", null ]}
               ]}
        }}
    }}
]);

这会产生一个文档结果(我添加了一个额外的文档 _id 41 来测试超过 1 个文档的 null 相等性):

{
    "_id" : null,
    "X" : [
        {
            "_id" : "4",
            "dateA" : ISODate("2022-01-09T00:00:00Z"),
            "dateB" : null
        },
        {
            "_id" : "41",
            "dateA" : ISODate("2022-01-09T00:00:00Z"),
            "dateB" : null
        }
    ]
}

在此之后 $unwind$replaceRoot 是可能的,但几乎没有必要这样做。