【问题标题】:MongoDB aggregate/grouping by key-value pairsMongoDB 按键值对聚合/分组
【发布时间】:2016-07-03 20:32:10
【问题描述】:

我的数据如下所示:

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

所以基本上对于示例数据,该组应该给我一个分组:

   {"customerId": "2164289-4", "channel": "BAR", "status": "error"}

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

【问题讨论】:

    标签: mongodb mapreduce mongodb-query aggregation-framework


    【解决方案1】:

    使用.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 阶段,因为第一个阶段通过$first 累加器在过滤器之后删除了"channel" 值的任何可能重复项。下面的$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 } }
    )
    

    你当然可以在哪里使用这种符号

    【讨论】:

    • 谢谢。我决定将 channelStatuses 更改为您描述的数组。有没有更简单的方法来获取正确的频道,比如说首先展开 channelStatuses,然后进行匹配(“channelStatuses.channel”:“$channels”),然后按状态分组?
    • @Keksike 像$filter$map 这样的操作的全部要点是“避免”使用$unwind$unwind 操作符本质上是一个“巨大的”性能猪。除非您打算从包含在数组中的值跨文档聚合,否则您应该避免使用$unwind。你可以做同样的事情,但成本要高得多。最好的方法是显示的方法。
    • 您对如何在没有 arrayElemAt 的情况下进行分组有任何想法,因为我使用的 mongodb-version 不支持它?
    • @Keksike 你有什么?至少 MongoDB 2.6.x?
    • @Keksike 添加了所有内容。
    猜你喜欢
    • 2014-05-10
    • 1970-01-01
    • 2015-03-28
    • 2021-09-14
    • 2021-05-19
    • 2013-11-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    相关资源
    最近更新 更多