从日期在今天之前的数组聚合

时间:2016-02-09 23:17:24

标签: javascript mongodb mongodb-query aggregation-framework

我正在尝试聚合具有数组的集合。在这个数组中,有一个reminders数组。该文件可能如下所示:

{
  _id: "1234",
  dates: {
    start: ISODate(),
    end: ISODate()
  },
  reminders: [{
    sendAt: ISODate(),
    status: 'closed'
  }, {
    sendAt: ISODate(),
    status: 'open'
  }]
}

说第一个是今天之前,下一个是今天之后。我想要做的是获取今天之前的所有数组,或者,如果今天之前没有数组,则为空数组。我尝试了以下聚合

db.reminders.aggregate([
  { $match: { 'dates.end': { $gt: new Date } } },
  { $unwind: '$reminders' },
  {
    $match: {
      reminders: {
        $elemMatch: {
          sendAt: { $lt: new Date() },
          status: { $ne: 'open' }
        }
      }
    }
  }
])

但是,如果在今天之前没有提醒,它将会失败并且不予以回复。

有没有办法用mongodb聚合构建这个结构?

注意:我无法使用$filter,因为这是在3.2

2 个答案:

答案 0 :(得分:1)

您可以使用enter image description here运算符过滤掉版本>=2.6的子文档。 它还避免了不必要的$unwind阶段。

  • $match所有dates.end属性大于搜索条件的文档。
  • $redact通过所有子文档并执行以下操作,$redact到符合条件的文档中,否则$$DESCEND

示例代码:

var endDateToMatch = ISODate("2014-01-01T00:00:00Z");
var currentDate = ISODate();

db.t.aggregate([
{
  $match:{"dates.end":{$gt:endDateToMatch}}
},
{
  $redact:{$cond:[
                 {$and:[
                        {$ne:[{$ifNull:["$status",""]},
                              "open"]},
                        {$lt:[{$ifNull:["$sendAt",currentDate-1]},
                              currentDate]}
                       ]
                 },
                "$$DESCEND","$$PRUNE"]}
}
])

这将为每个与$match阶段匹配的文档提供一个文档。如果您需要累积所有子文档,则需要$unwind“提醒”和$group _idnull

答案 1 :(得分:1)

所以你基本上想要$filter行为但是需要在早期版本中执行它,主要情况是返回文档,即使数组内容最终为空。

对于MongoDB 2.6,您可以使用$map$setDifference“几乎”做同样的事情:

db.reminders.aggregate([
    { "$match": { "dates.end": { "$gt": new Date() } } },
    { "$project": {
        "dates": 1,
        "reminders": {
            "$setDifference": [
                { "$map": {
                    "input": "$reminders",
                    "as": "reminder",
                    "in": {
                        "$cond": [
                            { "$and": [
                                { "$lt": [ "$$reminder.sendAt", new Date() ] },
                                { "$ne": [ "$$reminder.status", "open" ] }
                            ]},
                            "$$reminder",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

只要$setDifference生成的“设置”都是未经识别的项目,那就没问题。因此,$map方法应用测试,要么返回内容,要么false如果条件不匹配。 $setDifferene基本上会删除结果中的所有false元素,但当然“set”会将任何项目与一个项目完全相同。

如果您的MongoDB小于2.6(或“sets”的情况使上述情况无法使用),那么在查看要过滤的内容时只需要更加小心:

db.reminders.aggregate([
    { "$match": { "dates.end": { "$gt": new Date() } } },
    { "$unwind": "$reminders" },

    // Count where condition matched
    { "$group": {
        "_id": "$_id",
        "dates": { "$first": "$dates" },
        "reminders": { "$push": "$reminders" },
        "matched": { "$sum": {
            "$cond": [
                { "$and": [
                    { "$lt": [ "$reminders.sendAt", new Date() ] },
                    { "$ne": [ "$reminders.status", "open" ] }
                 ]},
                 1,
                 0
            ]
        }}
    }},

    // Substitute array where no count just for brevity
    { "$project": {
        "dates": 1,
        "reminders": { "$cond": [
            { "$eq": [ "$matched", 0 ] },
            { "$const": [false] },
            "$reminders"
        ]},
        "matched": 1
    }},

    // Unwind again
    { "$unwind": "$reminders" },

    // Filter for matches "or" where there were no matches to keep
    { "$match": {
        "$or": [
            { 
                "reminder.sendAt": { "$lt": new Date() },
                "reminder.status": { "$ne": "open" }
            },
            { "matched": 0 }      
        ]
    }},

    // Group again
    { "$group": {
        "_id": "$_id",
        "dates": { "$first": "$dates" },
        "reminders": { "$push": "$reminders" }
    }},

    // Replace the [false] array with an empty one
    { "$project": {
        "dates": 1,
        "reminders": { "$cond": [
            { "$eq": [ "$reminders", [false] ] },
            { "$const": [] },
            "$reminders"
        ]}
    }}
])

这有点长啰嗦,但它基本上做同样的事情。

另请注意,$elemMatch在处理$unwind后不适用,因为内容实际上不再是数组。简单的点表示法适用于现在在单个文档中的元素。