在聚合输出中组合不同的分组总计

时间:2015-12-15 02:02:22

标签: node.js mongodb mongoose mongodb-query aggregation-framework

现在,我已经在$ project,aggregate()和$ group上度过了一个周末,现在是时候再次投入自己的怜悯了。我尝试拨打电话,在那里我收回用户的总数,按性别分组(这是更容易的部分)并按年龄范围分组(这打败了我)。

我让它与一个小组合作:

        Person.aggregate([
            {
                $match: {
                    user_id: id
                }
            },
            {
                $group: {
                        _id: '$gender',
                        total: { $sum: 1 }
                }
            }
        ])
        .exec(function(err, result) {
                etc...

从那以后,它会给我多少男人,一个漂亮的json输出中有多少女人。但是,如果我添加第二组,它似乎跳过了第一组并且在第二组中抛出了他的错误:

        Person.aggregate([
            {
                $match: {
                    user_id: id
                }
            },
            {
                $group: {
                        _id: '$gender',
                        total: { $sum: 1 }
                },
                $group: {
                        _id: '$age',
                        age: { $gte: 21 },
                        age: { $lte: 30 },
                        total: { $sum: 1 }
                }
            }
        ])
        .exec(function(err, result) {
                etc...

它不喜欢$ gte或$ lte。如果我将它切换到$ project,那么它会执行gte / lte但是抛出大约$ sum或$ count。最重要的是,我无法找到如何构建多请求返回的任何示例。它只是"这是一件事,"但我不想做12个以上的电话只是为了获得所有人的年龄组。我希望输出看起来像这样:

    [
        {"_id":"male","total":49},
        {"_id":"woman","total":42},
        {"_id":"age0_10", "total": 1},
        {"_id":"age11_20", "total": 5},
        {"_id":"age21_30", "total": 15}
    ]

(我不知道如何让年龄的_id不是实际的年龄,这没有意义,b / c我不想要1517191919或其他什么,我想要一个可靠的名字,所以我知道在我的模板中输出它的位置。所以我知道_id:" $ age"不能给我我想要的东西,但我不知道如何得到我想要的东西。)

我唯一一次见过不止一件事,那就是$ match,$ group和$ project。但是如果$ project意味着我不能使用$ sum或$ count,我可以做多个$ group,如果可以的话,它的诀窍是什么?

1 个答案:

答案 0 :(得分:1)

对于在不同年龄组中生成结果的情况,聚合框架的$cond运算符可以在此处提供帮助。作为三元运算符,它采用逻辑结果(如果条件)并且可以返回true(然后)或其他false(其他)的值。在不同年龄组的情况下,你会"筑巢" else条件下的调用,以满足每个范围,直到逻辑耗尽。

对于"性别"两个结果,一次性传球的整体情况并不实际。和"年龄"在分组中。虽然"可以" 完成,但唯一的方法是基本上累积数组中的所有数据,并再次为次要分组工作。这不是一个好主意,因为它在尝试保留数据时几乎总是会破坏16MB的实际BSON限制。因此通常需要更好的方法。

因此,在API支持的地方(你在nodejs之下,它确实如此),通常最好分别运行每个查询并组合结果。节点async库具有以下功能:

async.concat(
    [
        // Gender aggregator
        [
            { "$group": {
                "_id": "$gender",
                "total": { "$sum": 1 }
            }}
        ],
        // Age aggregator
        [
            { "$group": {
                "_id": {
                    "$cond": {
                        "if": { "$lte": [ "$age", 10 ] },
                        "then": "age_0_10",
                        "else": {
                            "$cond": {
                               "if": { "$lte": [ "$age", 20 ] },
                               "then": "age_11_20",
                               "else": {
                                   "$cond": {
                                       "if": { "$lte": [ "$age", 30 ] },
                                       "then": "age_21_30",
                                       "else": "age_over_30"
                                   }
                               }
                            }
                        }
                    }
                },
                "total": { "$sum": 1 }
            }}
        ]
    ],
    function(pipeline,callback) {
        Person.aggregate(pipeline,callback);
    },
    function(err,results) {
        if (err) throw err;
        console.log(results);
    }
);

此处async.concat的默认执行将启动并行运行的任务,因此两者可以同时在服务器上运行。输入数组中的每个管道都将传递给aggregate方法,然后返回结果并将输出数组合并到最终结果中。

最终结果是,您不仅可以将结果很好地键入年龄组,而且两个结果集似乎在同一个组合响应中,而不需要其他工作来合并内容。

这不仅方便,而且并行执行使得这更加节省时间,并且在用于返回结果的聚合方法上减少了(如果没有击败不可能的话)。