MongoDB按键值对进行聚合/分组

时间:2016-03-17 13:07:54

标签: mongodb mapreduce mongodb-query aggregation-framework

我的数据看起来像这样:

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

@IBAction func NextViewcontroller(sender: AnyObject)
{
    let startVC = self.viewControllerAtIndex(0) as ContentViewController
    let viewControllers = NSArray(object: startVC)

    self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)


}

func viewControllerAtIndex(index: Int) -> ContentViewController
{
    if ((self.pageTitles.count == 0) || (index >= self.pageTitles.count)) {
        return ContentViewController()
    }

    let vc: ContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController

    vc.imageFile = self.pageImages[index] as! String
    vc.titleText = self.pageTitles[index] as! String
    vc.pageIndex = index

    return vc

}



// MARK: - Page View Controller Data Source

func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{

    let vc = viewController as! ContentViewController
    var index = vc.pageIndex as Int


    if (index == 0 || index == NSNotFound)
    {
        return nil

    }

    index--
    return self.viewControllerAtIndex(index)

}

func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

    let vc = viewController as! ContentViewController
    var index = vc.pageIndex as Int

    if (index == NSNotFound)
    {
        return nil
    }

    index++

    if (index == self.pageTitles.count)
    {
        return nil
    }

    return self.viewControllerAtIndex(index)

}

func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
    return self.pageTitles.count
}

func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
    return 0
}
}

我的聚合/群组如下所示:

    { 
            "_id" : "9aa072e4-b706-47e6-9607-1a39e904a05a", 
            "customerId" : "2164289-4", 
            "channelStatuses" : {
                    "FOO" : {
                    "status" : "done"
                    }, 
                    "BAR" : {
                    "status" : "error"
                    }
            }, 
            "channel" : "BAR", 
    }

所以基本上使用示例数据,该组应该给我一个按以下方式分组的组:

    { 
            "_id" : {
                    "customerId" : "$customerId", 
                    "channel" : "$channel", 
                    "status" : "$channelStatuses[$channel].status"
            }, 
                    "count" : {
                    "$sum" : 1
            }
    }

但是我不能在聚合/组中使用[] -indexing。我该怎么做呢?

1 个答案:

答案 0 :(得分:2)

使用.aggregate()无法获得当前结构所需的结果。你"可能"更改结构以使用数组而不是命名键,操作实际上非常简单。

所以使用如下文档:

    { 
            "_id" : "9aa072e4-b706-47e6-9607-1a39e904a05a", 
            "customerId" : "2164289-4", 
            "channelStatuses" : [
                {
                    "channel": "FOO",
                    "status" : "done"
                }, 
                {
                    "channel": "BAR",
                    "status" : "error"
                }
            ], 
            "channel" : "BAR", 
    }

然后,您可以使用$filter$map$arrayElemAt在现代版本中执行此操作:

    { "$group": {
        "_id": {
            "customerId" : "$customerId", 
            "channel" : "$channel", 
            "status": {
                "$arrayElemAt": [
                    { "$map": {
                        "input": { "$filter": {
                            "input": "$chanelStatuses",
                            "as": "el", 
                            "cond": { "$eq": [ "$$el.channel", "$channel" ] }
                        }},
                        "as": "el",
                        "in": "$$el.status"
                    }},
                    0
                ]
            }
        },
        "count": { "$sum": 1 }
    }}

较旧版本的MongoDB将需要$unwind来访问匹配的数组元素。

在MongoDB 2.6中,您仍然可以预先过滤"展开前的数组:

[
    { "$project": {
        "customerId": 1,
        "channel": 1,
        "status": {
            "$setDifference": [
                { "$map": {
                    "input": "$channelStatuses",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.channel", "$channel" ] },
                            "$$el.status",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }},
    { "$unwind": "$status" },
    { "$group": {
        "_id": {
            "customerId": "$customerId",
            "channel": "$channel",
            "status": "$status"
        },
        "count": { "$sum": 1 }
    }}
]

之前的任何事情你都要过滤"在$unwind后改为:

[
    { "$unwind": "$channelStatuses" },
    { "$project": {
        "customerId": 1,
        "channel": 1,
        "status": "$channelStatuses.status",
        "same": { "$eq": [ "$channelStatuses.status", "$channel" ] }
    }},
    { "$match": { "same": true } },
    { "$group": {
        "_id": "$_id",
        "customerId": { "$first": "$customerId" },
        "channel": { "$first": "$channel" },
        "status": { "$first": "$status" }
    }},
    { "$group": {
        "_id": {
            "customerId": "$customerId",
            "channel": "$channel",
            "status": "$status"
        },
        "count": { "$sum": 1 }
    }}
]

在比MongoDB 2.6更小的版本中,您还需要$project两个字段之间的相等性测试结果,然后$match结果处于单独的阶段。您可能还会注意到"两个" $group个阶段,因为第一个阶段通过"channel"累加器删除了过滤后的$first值的任何可能重复项。以下$group与上一个列表中的完全相同。

但是,如果你不能改变结构和需要"灵活"如果您不能提供每个名称的键匹配,那么您必须使用mapReduce:

db.collection.mapReduce(
    function() {
       emit({
           "customerId": this.customerId,
           "channel": this.channel,
           "status": this.channelStatuses[this.channel].status
       },1);
    },
    function(key,values) {
        return Array.sum(values);
    },
    { "out": { "inline": 1 } }
)

当然,你可以使用那种符号