mongodb - mongo $sum 在执行 $unwind 然后在多个字段上执行 $group 时复合

标签 mongodb mongodb-query aggregation-framework

我有以下文档结构

{
    "app_id": "DHJFK67JDSJjdasj909",
    "date": ISODate("2014-08-07T00:00:00.000Z"),
    "event_count": 100,
    "events": [
        { "type": 0,  "value": 12  },
        { "type": 10, "value": 24 },
        { "type": 20, "value": 36  },
        { "type": 30, "value": 43 }
    ],
    "unique_events": [
        { "type": 0,  "value": 5  },
        { "type": 10, "value": 8 },
        { "type": 20, "value": 12  },
        { "type": 30, "value": 56 }
    ]
}

我正在尝试获取 event_counts 的总和以及每个类型的 unique_events 和事件的值。这是我期望的输出类型,其中 event_count 以及每个事件和 unique_events 值已按类型求和。

{
    "app_id": "DHJFK67JDSJjdasj909",
    "date": ISODate("2014-08-07T00:00:00.000Z"),
    "event_count": 4345,
    "events": [
        { "type": 0,  "value": 624  },
        { "type": 10, "value": 234 },
        { "type": 20, "value": 353 },
        { "type": 30, "value": 472 }
    ],
    "unique_events": [
        { "type": 0,  "value": 433  },
        { "type": 10, "value": 554 },
        { "type": 20, "value": 645  },
        { "type": 30, "value": 732 }
    ]
}

这是我的查询

db.events.aggregate([
    { "$unwind": "$events" },
    { "$group": {
        "_id": { 
            "app_id": "$app_id",
            "type": "$events.type"
            "unique_type": "$unique_events.type"
        },
        "event_count": { "$sum": "$event_count" },
        "event_value": { "$sum": "$events.value" },
        "unique_event_value": { "$sum": "$unique_events.value" }
    }},
    { "$group": {
        "_id": "$_id.app_id",
        "event_count": { "$sum": "$event_count" },
        "events": { "$push": { "type": "$_id.type", "value": "$event_value" } }
        "unique_events": { "$push": { "type": "$_id.unique_type", "value": "$unique_event_value" } }
    }}
]) 

问题在于,使用两个 $unwinds 然后按事件和 unique_events 进行分组会导致 $sum 复合且太大。有什么方法可以使用 mongo 解决这个问题,还是我必须运行两个查询,然后在代码中合并两个结果集。

谢谢

伊尔凡

最佳答案

这真的很简单,要对每个数组的结果求和,只需区分哪个是哪个和“组合元素”。简而言之,您可能应该在您的文档中执行此操作,这从第一个管道阶段就可以看出。

所以对于 MongoDB 2.6 及更高版本,有一些辅助方法:

db.events.aggregate([
    { "$project": {
        "app_id": 1,
        "event_count": 1,
        "all_events": {
            "$setUnion": [
                { "$map": {
                    "input": "$events",
                    "as": "el",
                    "in": {
                        "type": "$$el.type",
                        "value": "$$el.value",
                        "class": { "$literal": "A" }
                    }
                }},
                { "$map": {
                    "input": "$unique_events",
                    "as": "el",
                    "in": {
                        "type": "$$el.type",
                        "value": "$$el.value",
                        "class": { "$literal": "B" }
                    }
                }}
            ]
        }
    }},
    { "$unwind": "$all_events" },
    { "$group": {
        "_id": {
            "app_id": "$app_id",
            "class": "$all_events.class",
            "type": "$all_events.type"
        },
        "event_count": { "$sum": "$event_count" },
        "value": { "$sum": "$all_events.value" }
    }},
    { "$group": {
        "_id": "$_id.app_id",
        "event_count": { "$sum": "$event_count" },
        "events": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.class", "A" ] },
                    { "type": "$_id.type", "value": "$value" },
                    false
                ]
            }
        },
        "unique_events": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.class", "B" ] },
                    { "type": "$_id.type", "value": "$value" },
                    false
                ]
            }
        }
    }},
    { "$project": {
        "event_count": 1,
        "events": { "$setDifference": [ "$events", [false] ] },
        "unique_events": {
            "$setDifference": [ "$unique_events", [false] ]
        }
    }}
])

主要在 $setUnion$setDifference运营商。另一个 ccase 是 $map ,它在适当的位置处理数组。整个过程是在不使用 $unwind 的情况下对数组进行操作。 .但是这些当然可以在以前的版本中完成,只是需要更多的工作:

db.events.aggregate([
    { "$unwind": "$events" },
    { "$group": {
        "_id": "$_id",
        "app_id": { "$first": "$app_id" },
        "event_count": { "$first": "$event_count" },
        "events": {
            "$push": {
                "type": "$events.type",
                "value": "$events.value",
                "class": { "$const": "A" }
            }
        },
        "unique_events": { "$first": "$unique_events" }            
    }},
    { "$unwind": "$unique_events" },
    { "$group": {
        "_id": "$_id",
        "app_id": { "$first": "$app_id" },
        "event_count": { "$first": "$event_count" },
        "events": { "$first": "$events" },
        "unique_events": {
            "$push": {
                "type": "$unique_events.type",
                "value": "$unique_events.value",
                "class": { "$const": "B" }
            }
        }
    }},
    { "$project": {
        "app_id": 1,
        "event_count": 1,
        "events": 1,
        "unique_events": 1,
        "type": { "$const": [ "A","B" ] }
    }},
    { "$unwind": "$type" },
    { "$unwind": "$events" },
    { "$unwind": "$unique_events" },
    { "$group": {
        "_id": "$_id",
        "app_id": { "$first": "$app_id" },
        "event_count": { "$first": "$event_count" },
        "all_events": {
            "$addToSet": {
                "$cond": [
                     { "$eq": [ "$events.class", "$type" ] },
                     {
                         "type": "$events.type",
                         "value": "$events.value",
                         "class": "$events.class"
                     },
                     {
                         "type": "$unique_events.type",
                         "value": "$unique_events.value",
                         "class": "$unique_events.class"
                     }
                ]
            }
        }
    }},
    { "$unwind": "$all_events" },
   { "$group": {
        "_id": {
            "app_id": "$app_id",
            "class": "$all_events.class",
            "type": "$all_events.type"
        },
        "event_count": { "$sum": "$event_count" },
        "value": { "$sum": "$all_events.value" }
    }},
    { "$group": {
        "_id": "$_id.app_id",
        "event_count": { "$sum": "$event_count" },
        "events": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.class", "A" ] },
                    { "type": "$_id.type", "value": "$value" },
                    false
                ]
            }
        },
        "unique_events": {
            "$push": {
                "$cond": [
                    { "$eq": [ "$_id.class", "B" ] },
                    { "type": "$_id.type", "value": "$value" },
                    false
                ]
            }
        }
    }},
    { "$unwind": "$events" },
    { "$match": { "events": { "$ne": false } } },
    { "$group": {
        "_id": "$_id",
        "event_count": { "$first": "$event_count" },
        "events": { "$push": "$events" },
        "unique_events": { "$first": "$unique_events" }
    }},
    { "$unwind": "$unique_events" },
    { "$match": { "unique_events": { "$ne": false } } },
    { "$group": {
       "_id": "$_id",
        "event_count": { "$first": "$event_count" },
        "events": { "$first": "$events" },
        "unique_events": { "$push": "$unique_events" }
    }}
])

这将为您提供所需的结果,每个数组“求和”在一起,以及具有正确结果的主“event_count”。

您可能应该考虑将这两个数组与在管道中使用的标识符相似的标识符组合在一起,如图所示。这部分是工作的一半。另一半考虑您可能应该将预先聚合的结果存储在某个集合中以获得最佳应用程序性能。

关于mongodb - mongo $sum 在执行 $unwind 然后在多个字段上执行 $group 时复合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25201157/

相关文章:

node.js - 使用索引时MongoDB查询排序超出内存限制

Mongodb在创建新用户时出错

mongodb - 获取列表中带有标签的文档,按匹配总数排序

mongodb - 在 mongodb 聚合框架中组合多个 match() 结果

mongodb - 聚合管道抛出错误 "A pipeline stage specification object must contain exactly one field."

c# - 如何从 mongo 文档中删除 _v 和 _t

node.js - 使用 mongodb 更新复杂对象

javascript - 如何在 Meteor 中发布一个只发送最近 24 小时数据给客户端的集合?

MongoDB 存储和查询价格历史记录

node.js - Node , Mongoose : Remove json element from query result with delete