【问题标题】:Combine Different Grouping Totals in Aggregate Output在汇总输出中组合不同的分组总计
【发布时间】:2015-12-15 12:40:49
【问题描述】:

现在我已经在 $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,我想要一个可靠的名字所以我知道在我的模板中将它输出到哪里。所以我知道 _id: "$age" 不会给我我想要的东西,但我也不知道如何得到我想要的东西。)

我唯一一次看到不止一个东西,那就是 $match、$group 和 $project。但是如果 $project 意味着我不能使用 $sum 或 $count,我可以做多个 $groups,如果可以,它的诀窍是什么?

【问题讨论】:

  • 在某种程度上(第二部分)您似乎在寻找$cond,您可以使用它来处理逻辑结果以生成您的年龄密钥。但在另一部分,你想要一个“多面”的结果,“性别”和“年龄”的分组结果都是一个响应。在一个查询中实现这并不是很实际(并非完全不可能,但通常如此),并且通过组合至少两个查询的结果来更好地服务。一种用于性别,一种用于年龄组。
  • 当你说'查询'时,你的意思是两个电话吗?还是我做一个 Person.aggregate,获取该数据,保存到结果集,然后再做一个 Person.aggregate? (我实际上还没有想出如何将数据保存到我然后执行的集合中 - 而不仅仅是 .find().select().exec() - 但理论上它似乎是可能的。 ) 还是你的意思是别的?

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


【解决方案1】:

至于在不同年龄段产生结果的情况,聚合框架的$cond 操作符可以在这里提供帮助。作为三元运算符,它接受一个逻辑结果( if 条件)并且可以返回一个值 where true ( then ) 或 false ( else )。在不同年龄组的情况下,您可以将调用“嵌套”在 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的默认执行将启动任务并行运行,因此两者可以同时在服务器上运行。输入数组中的每个管道都将传递给聚合方法,该方法将返回结果并将输出数组组合成最终结果。

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

这不仅方便,而且并行执行使用于返回结果的聚合方法的时间效率更高,而且负担更少(如果没有击败不可能的话)。

【讨论】:

  • ohhhhh [灯泡亮起] 然后我像对待函数(管道,回调)一样对待(函数(错误,结果)并从那里执行通常的 try/catch 和 res.send(回调) ? 另一件事:我会将 $match 部分放在最顶部,以确保我只为每个集合获取正确的 userId,还是放在每个 $group 中?
  • ahaha 忽略第一个问题,只是意识到我没有在示例中一直向下滚动,我看到了其余部分。 :)
  • 忽略第二部分,我也想出了 $match 部分。非常感谢您为我指明了正确的方向!
猜你喜欢
  • 1970-01-01
  • 2022-01-27
  • 2014-11-25
  • 2018-10-03
  • 1970-01-01
  • 1970-01-01
  • 2021-08-23
  • 1970-01-01
  • 2015-09-02
相关资源
最近更新 更多