MongoDB聚合 - 使用lte和fallback匹配gte

时间:2015-05-27 08:35:39

标签: mongodb mongodb-query

我有一个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获得任何实例。在这种情况下,我想检索文档中可用的最早实例。

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

有什么想法吗?

1 个答案:

答案 0 :(得分:3)

管道

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

[
   {$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支持文档,同时$summatches字段提升到新的hasMatches。生成的文档现在包含hasMatches字段,表示instances数组是否包含至少一个符合条件的元素。

然后,我们再次$unwind$project,然后再次$group,保留hasMatches字段并同时存储$first$last datevaluescore的值,以便进一步处理。

现在的情况如下:

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

  • 如果初始数组中有元素符合条件,则在排序结果中,具有最早日期的元素显示为 last 小组中的文件。

因此,由于我们有hasMatches字段指示上述条件,以及first_Xlast_X值,我们可以轻松选择其中一个,具体取决于{ {1}}价值。因此,最后一个hasMatches阶段就是这样做的。

结果

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

&#39; 2015年3月4日&#39;

$project

&#39; 2015年3月2日&#39;

{ "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 }