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

5

我的数据大致长这样:

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

但是我无法在聚合/分组中使用[]索引。那我该怎么办呢?

1个回答

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阶段,因为第一个阶段通过$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"),然后只需按状态分组? - Cihan Bebek
1
@Keksike,像 $filter$map 这样的操作的整个目的就是“避免”使用 $unwind。由于 $unwind 所做的事情的本质,它是一个“巨大”的性能瓶颈。除非您打算从数组中包含的值跨文档进行聚合,否则应避免使用 $unwind。您可以做同样的事情,但成本要高得多。最好的方法是所示的方法。 - Blakes Seven
你有没有任何想法如何进行分组,而不使用arrayElemAt,因为我正在使用的mongodb版本不支持它? - Cihan Bebek
1
@Keksike 你有什么?至少有MongoDB 2.6.x吗? - Blakes Seven
1
@Keksike 已添加所有内容。 - Blakes Seven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接