在 Mongodb 中聚合嵌套数组

Aggregate nested array in Mongodb

我有一个mongocollection谎言这个:

    {
   "_id":ObjectId("55f16650e3cf2242a79656d1"),
   "user_id":11,
   "push":[
      ISODate("2015-09-08T11:14:18.285      Z"),
      ISODate("2015-09-08T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z")
   ]
}{
   "_id":ObjectId("55f15c78e3cf2242a79656c3"),
   "user_id":12,
   "push":[
      ISODate("2015-09-06T11:14:18.285      Z"),
      ISODate("2015-09-05T11:14:18.285      Z"),
      ISODate("2015-09-07T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-09T11:14:18.285      Z"),
      ISODate("2015-09-10T11:14:18.285      Z"),
      ISODate("2015-09-11T11:14:18.285      Z")
   ]
}

如何在单个查询中找到 user_ids timeStamps < 3 且日期 (timestamp) > (currentDate-5) 的位置。我将使用 php 并且不想将所有文档都放在内存中。

说明:

user_id : date       : count
11      : 2015-09-08 : 2
          2015-09-09 : 1
          2015-09-10 : 2

12      : 2015-09-05 : 1
          2015-09-06 : 1
          2015-09-07 : 1
          2015-09-09 : 2
          2015-09-10 : 1
          2015-09-11 : 1

如果日期设置为 2015-09-09(用户输入),它将为 user_id 11 提供 3(计数),为 user_id 12 提供 4(计数)。所以假设设置了计数到 3(用户输入)。查询应该 return 11(user_id)。如果计数设置为 2,将没有 user_id 可用,如果计数设置为 5,它应该 return 11 和 12

为了解决这个问题,您需要一个聚合管道,它首先 "filters" 将结果 "last 5 days" 然后基本上 "sums the count" 每个符合条件的文档中存在的数组项,然后查看是否"total" 是 "less than three".

所需的 $size operator of MongoDB aggregation really helps here, as does $map and some additional filtering via $setDifference for the false results returned from $map, as doing this "in document first" and "within" the $group 阶段是处理此问题的最有效方法

$result = $collection->aggregate(array(
    array( '$match' => array(
        'push' => array( 
            'time' => array( 
                '$gte' =>  MongoDate( strtotime('-5 days',time()) )
            )
        )     
    )),
    array( '$group' => array(
        '_id' => '$user_id',
        'count' => array(
            '$sum' => array(
                '$size' => array(
                    '$setDifference' => array(
                        array( '$map' => array(
                            'input' => '$push',
                            'as' => 'time',
                            'in' => array(
                                '$cond' => array(
                                    array( '$gte' => array(
                                        '$$time',
                                        MongoDate( strtotime('-5 days',time()) )
                                    )),
                                    '$time',
                                    FALSE
                                )
                            ) 
                        )),
                        array(FALSE)
                    )
                )
            )
        )
    )),
    array( '$match' => array(
        'count' => array( '$lt' => 3 )
    )) 
));

所以首先要通过$match找到包含满足条件的数组条目的"possible"文档,然后找到匹配数组项的"total"大小在$group下,那么最后的$match排除了所有总大小小于3的结果。


对于大部分 "JavaScript brains" 的人(像我自己一样,训练有素)基本上是这样的结构:

db.collection.aggregate([
    { "$match": {
        "push": {
            "$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
        }
    }},
    { "$group": {
        "_id": "$user_id",
        "count": {
            "$sum": {
                "$size": {
                    "$setDifference": [
                        { "$map": {
                            "input": "$push",
                            "as": "time",
                            "in": {
                                "$cond": [
                                    { "$gte": [ 
                                        "$$time",
                                        new Date( 
                                            new Date().valueOf() - 
                                            ( 5 * 1000 * 60 * 60 * 24 )
                                        )
                                    ]},
                                    "$$time",
                                    false
                                ]
                            }
                        }},
                        [false]
                    ]
                }
            }
        }
    }},
    { "$match": { "count": { "$lt": 3 } } }
])

此外,MongoDB 的未来版本将提供 $filter,这将简化整个 $map$setDifference 语句部分:

db.collection.aggregate([
    { "$match": {
        "push": {
            "$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
        }
    }},
    { "$group": {
        "_id": "$user_id",
        "count": {
            "$sum": {
                "$size": {
                    "$filter": {
                        "input": "$push",
                        "as": "time",
                        "cond": {
                            "$gte": [
                                "$$time",
                                new Date( 
                                    new Date().valueOf() - 
                                    ( 5 * 1000 * 60 * 60 * 24 )
                                )                       
                            ]
                        }
                    }
                }
            }
        }
    }},
    { "$match": { "count": { "$lt": 3 } } }
])

同时注意到 "dates" 可能是最好的计算 "before" 管道定义作为一个单独的变量以获得最佳准确性。