使用聚合框架进行分组和计数

时间:2015-01-18 17:27:08

标签: mongodb mongodb-query aggregation-framework

我正在尝试对以下结构进行分组和计算:

[{
    "_id" : ObjectId("5479c4793815a1f417f537a0"),
    "status" : "canceled",
    "date" : ISODate("2014-11-29T00:00:00.000Z"),
    "offset" : 30,
    "devices" : [ 
        {
            "name" : "Mouse",
            "cost" : 150,
        }, 
        {
            "name" : "Keyboard",
            "cost" : 200,
        }
    ],
},
{
    "_id" : ObjectId("5479c4793815a1f417d557a0"),
    "status" : "done",
    "date" : ISODate("2014-10-20T00:00:00.000Z"),
    "offset" : 30,
    "devices" : [ 
        {
            "name" : "LCD",
            "cost" : 150,
        }, 
        {
            "name" : "Keyboard",
            "cost" : 200,
        }
    ],
}
,
{
    "_id" : ObjectId("5479c4793815a1f417f117a0"),
    "status" : "done",
    "date" : ISODate("2014-12-29T00:00:00.000Z"),
    "offset" : 30,
    "devices" : [ 
        {
            "name" : "Headphones",
            "cost" : 150,
        }, 
        {
            "name" : "LCD",
            "cost" : 200,
        }
    ],
}]

我需要小组并计算类似的东西:

 "result" : [ 
        {
            "_id" : {
                "status" : "canceled"
            },
            "count" : 1
        }, 
        {
            "_id" : {
                "status" : "done"
            },
            "count" : 2
        },
    totaldevicecost: 730,

    ],
    "ok" : 1
}

我在计算子阵列“设备”中的成本总和时遇到的问题。怎么做?

1 个答案:

答案 0 :(得分:1)

看起来你有一个开始,但你迷失了一些其他概念。在文档中使用数组时有一些基本的事实,但是让我们从你离开的地方开始:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 }
    }}
])

这就是使用$group管道来收集关于" status"的不同值的文档。字段然后还产生另一个字段" count"当然"计数"通过将1的值传递给找到的每个文档的$sum运算符,可以出现分组键。这使您处于与您描述的非常相似的地步:

{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }

这是第一阶段并且很容易理解,但现在你需要知道如何从数组中获取值。一旦你理解了"dot notation"概念就可以做出类似的事情,那么你可能会受到诱惑:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

但你会发现"总计"实际上每个结果都会0

{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }

为什么呢?像这样的MongoDB聚合操作在分组时实际上并不遍历数组元素。为此,聚合框架有一个名为$unwind的概念。这个名字相对不言自明。 MongoDB中的嵌入式数组就像拥有一对多的#34;链接数据源之间的关联。那么$unwind所做的正是那种"加入"结果,由此产生的"文件"基于数组的内容和每个父级的重复信息。

因此,为了对数组元素进行操作,您需要先使用$unwind。这应该在逻辑上引导您进行如下代码:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

然后结果:

{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }

但这不是很正确吗?还记得你刚刚从$unwind学到了什么,以及它如何与父信息进行非规范化连接?所以现在每个文档都有重复,因为它们都有两个数组成员。所以虽然"总计"字段是正确的,"计数"是每种情况下的两倍。

需要更加小心,所以不要在单个$group阶段进行,而是在两个阶段完成:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }}
])

现在获得的结果是正确的总数:

{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }

现在数字是正确的,但它仍然不是你要求的。我认为你应该停在那里,因为你期望的那种结果实际上并不适合单独聚合的单一结果。你正在寻找内部"内部"结果。它真的不属于那里,但对于小数据,它是可以的:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }},
    { "$group": {
        "_id": null,
        "data": { "$push": { "count": "$count", "total": "$total" } },
        "totalCost": { "$sum": "$total" }
    }}
])

最终结果形式:

{
    "_id" : null,
    "data" : [
            {
                    "count" : 1,
                    "total" : 350
            },
            {
                    "count" : 2,
                    "total" : 700
            }
    ],
    "totalCost" : 1050
}

但是,"不要这样做" 。 MongoDB的响应文件限制为16MB,这是BSON规范的限制。在小结果上你可以做这种方便包装,但是在更大的方案中,你希望结果是早期的形式,或者是单独的查询,或者实时迭代整个结果,以便从所有文档中获得总数。 / p>

您似乎使用的是小于2.6的MongoDB版本,或者从不支持最新版本功能的RoboMongo shell复制输出。从MongoDB 2.6虽然聚合的结果可以是一个"游标"而不是单个BSON阵列。因此整体响应可能远大于16MB,但只有当您没有压缩到单个文档时才会显示结果,如上一个示例所示。

在你是"分页"的情况下尤其如此。结果,100到100个结果行,但你只想要一个"总计"当您只返回"页面时返回API响应"一次25个结果。

无论如何,这应该为您提供合理的指导,帮助您了解如何从常用文档表单中获得所期望的结果类型。请记住$unwind以便处理数组,并且通常$group多次处理,以便从文档和集合分组中获得不同分组级别的总计。