【问题标题】:MongoDB aggregate - match using lte and fallback to gteMongoDB 聚合 - 使用 lte 匹配并回退到 gte
【发布时间】:2015-08-09 05:28:27
【问题描述】:

我有一个 MongoDB 集合,其结构(简化)如下:

[
  {
    "name" : "name1",
    "instances" : [ 
      {
        "value" : 1,
        "score" : 2,
        "date" : ISODate("2015-03-04T00:00:00.000Z")
      }, 
      {
        "value" : 2,
        "score" : 5,
        "date" : ISODate("2015-04-01T00:00:00.000Z")
      }, 
      {
        "value" : 2.5,
        "score" : 9,
        "date" : ISODate("2015-03-05T00:00:00.000Z")
      }
    ]
  },
  {
    "name" : "name2",
    "instances" : [ 
      {
        "value" : 6,
        "score" : 3,
        "date" : ISODate("2015-03-05T00:00:00.000Z")
      }, 
      {
        "value" : 1,
        "score" : 6,
        "date" : ISODate("2015-03-04T00:00:00.000Z")
      }, 
      {
        "value" : 3.7,
        "score" : 5.2,
        "date" : ISODate("2015-02-04T00:00:00.000Z")
      }
    ]
  },
  {
    "name" : "name3",
    "instances" : [ 
      {
        "value" : 6,
        "score" : 3,
        "date" : ISODate("2015-03-05T00:00:00.000Z")
      }, 
      {
        "value" : 1,
        "score" : 6,
        "date" : ISODate("2015-03-04T00:00:00.000Z")
      }, 
      {
        "value" : 3.7,
        "score" : 5.2,
        "date" : ISODate("2015-02-04T00:00:00.000Z")
      }
    ]
  }
]

目前我有一个aggregate 查询,它在给定日期之前从每个文档中提取一个实例:

db.myCollection.aggregate([
  {$unwind: "$instances"},      
  {$sort: {'instances.date': -1}},
  {$match: {'instances.date': {$lte: <givenDate>}}},
  {$project: {name: 1, _id: 0, date: "$instances.date", value: "$instances.value", score: "$instances.score"}},
  {$group: {_id: "$name", name: {$first: "$name"}, date: {$first: "$date"},
    value: {$first: "$value"}, score: {$first: "$score"}}}
])

这个查询工作得很好,对于给定的日期,将返回每个文档中最新的(即,恰好或之前的)实例。

当给定日期早于最早实例时,我的问题就开始了。例如,如果我给定的日期是 2015-03-02,我将不会从 name1 获得任何实例。在这种情况下,我想检索文档中可用的最早实例。

显然,我可以将此任务拆分为两个不同的查询并合并结果,但如果可能,我希望在单个数据库查询中实现此目标。

有什么想法吗?

【问题讨论】:

  • 在那种情况下,你为什么需要指定date?
  • 不确定我是否理解这个问题。我从用户选择中获取日期,我需要获取截至该日期的最新值。如果有些项目的日期太早,我想拿到最早的。
  • 我的意思如下。如果您有这种情况,则指定的date 没有意义。换句话说,无论您是否指定date,每个文档的结果都将是$first 日期。我错过了什么吗?
  • 在上面的示例 DB 中,如果 date 是 2015-03-04,那么结果集将包含来自 name1 的第一个实例,来自 name2 的第二个实例,以及第二个来自name3。如果 date 是 2015-03-02,则结果集将仅包含 2 个项目(来自 name2 和 name3 的第三个实例)。在这种情况下,我将希望从name1 中添加最早(在这种情况下是第一个)实例到该结果集中
  • 好的,我想现在我明白了。因此,您希望日期较早且 最接近您指定的日期。但是,如果文档没有这样的日期,请包括最早的日期(离现在最远的日期)?

标签: mongodb mongodb-query


【解决方案1】:

管道

试试这个管道,然后我们一步一步来:

[
   {$unwind: "$instances"},
   {$project: {
                 _id: 0, 
                 name: 1, 
                 date: '$instances.date', 
                 matches: {
                             $cond: [
                                       {$lte: ['$instances.date', new Date(<YOUR DATE>)]}, 
                                       1, 
                                       0
                             ]
                 }, 
                 score: '$instances.score', 
                 value: '$instances.value'
              }
   }, 
   {$group: {
                 _id: '$name', 
                 instances: {
                               $push: {
                                         date: '$date', 
                                         score: '$score', 
                                         value: '$value', 
                                         matches: '$matches'
                               }
                 }, 
                 hasMatches: {$sum: '$matches'}
            }
   }, 
   {$unwind: "$instances"}, 
   {$project: {
                 _id: 0, 
                 name: '$_id', 
                 date: '$instances.date', 
                 hasMatches: '$hasMatches', 
                 matches: '$instances.matches', 
                 score: '$instances.score', 
                 value: '$instances.value'
              }
   }, 
   {$sort: {'name': 1, 'matches': -1, 'date': -1}}, 
   {$group: {
                 _id: {name: '$name', hasMatches: '$hasMatches'}, 
                 last_date: {$last: '$date'}, 
                 last_score: {$last: '$score'}, 
                 last_value: {$last: '$value'}, 
                 first_date: {$first: '$date'}, 
                 first_score: {$first: '$score'}, 
                 first_value: {$first: '$value'}}
   }, 
   {$project: {
                 name: '$_id.name', 
                 date: {$cond: ['$_id.hasMatches', '$first_date', '$last_date']}, 
                 score: {$cond: ['$_id.hasMatches', '$first_score', '$last_score']}, 
                 value: {$cond: ['$_id.hasMatches', '$first_value', '$last_value']}, 
                 _id: 0}
   }
]

解释

第一个$unwind 和$project 阶段简单明了,我只添加了一个matches 字段,表明未受伤的文档是否符合您的条件。

然后我们$group 退回文档,同时$sum 将matches 字段添加到新的hasMatches。结果文档现在包含hasMatches 字段,该字段指示instances 数组是否包含至少一个符合您的条件的元素。

然后,我们再次$unwind 和$project,然后再次$group,保留hasMatches 字段并存储date、value 和@987654339 的$first 和$last 值@ 进行进一步处理。

现在情况如下:

  • 如果初始数组中至少有一个元素符合条件,则在排序结果中,它会作为其组中的第一个文档出现。

  • 如果初始数组中没有没有元素符合条件,那么在排序结果中,日期最早的元素出现在last 其组中的文档。

因此,由于我们有指示上述条件的 hasMatches 字段,以及 first_X 和 last_X 值,我们可以根据 hasMatches 值轻松选择其中之一。因此,最后一个 $project 阶段就是这样做的。

结果

以下是您在 cmets 中提到的日期的结果:

'2015-03-04':

{ "name" : "name3", "date" : ISODate("2015-03-04T00:00:00Z"), "score" : 6, "value" : 1 }
{ "name" : "name2", "date" : ISODate("2015-03-04T00:00:00Z"), "score" : 6, "value" : 1 }
{ "name" : "name1", "date" : ISODate("2015-03-04T00:00:00Z"), "score" : 2, "value" : 1 }

'2015-03-02':

{ "name" : "name3", "date" : ISODate("2015-02-04T00:00:00Z"), "score" : 5.2, "value" : 3.7 }
{ "name" : "name2", "date" : ISODate("2015-02-04T00:00:00Z"), "score" : 5.2, "value" : 3.7 }
{ "name" : "name1", "date" : ISODate("2015-03-04T00:00:00Z"), "score" : 2, "value" : 1 }

【讨论】:

    猜你喜欢
    • 2023-02-25
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 2016-11-17
    • 2016-12-23
    相关资源
    最近更新 更多