【问题标题】:Why does .aggregate() return keys in reverse order为什么 .aggregate() 以相反的顺序返回键
【发布时间】:2015-05-05 11:49:05
【问题描述】:

这已经困扰我一段时间了。 MongoDB 的aggregation framework 是一个很棒的工具,并且在大多数情况下通常比.mapReduce() 更适合使用,除非后者实际上更适合。

与 JavaScript 解释相比,它当然确实使用在本机 C++ 编译中实现的方法实际执行操作,因此在大多数情况下比 mapReduce 替代方案“更快”。

但这里的主要问题是“结果中的'反转'键顺序给出了什么?”。如果并非总是如此,那么至少在过去几个主要版本中都是如此(这里没有真正测试每个版本,在撰写本文时只有 2.6.x 和 3.x 候选版本)。但它总是处于“反向”状态,这似乎真的违反直觉,我将在一个例子中给出。

以基本集合为例:

db.example.insert([
    { "field": "A", "value": 1 },
    { "field": "A", "value": 2 },
    { "field": "B", "value": 3 },
    { "field": "B", "value": 4 },
    { "field": "C", "value": 5 },
    { "field": "C", "value": 6 }
])

一旦集合到位,当您想要运行这样的示例聚合操作时:

db.example.aggregate([
    { "$group": {
        "_id": "$field",
        "value": { "$sum": "$value" }
    }}
])

那么返回的结果总会像这样神秘地返回:

[
    { "_id" : "C", "value" : 11 },
    { "_id" : "B", "value" : 7 },
    { "_id" : "A", "value" : 3 }
]

这将始终保持一致,无论实际文档的插入顺序是什么,键都将“始终”以“相反的顺序”产生。

另一方面,现在让我们考虑一下.mapReduce() 做了什么。而且我不会直接引用一段文档,而是“原文社论”:

MapReduce 将始终将在处理之前发出的键排序到“reduce”函数作为一般优化。

或者基本上是这样说的。所以下面的代码:

db.example.mapReduce(
    function() {
        emit( this.field, this.value );
    },
    function(key,values) {
        return Array.sum( values );
    },
    { "out": { "inline": 1 } }
)

产生这种结果,并且符合记录行为的一般前提:

{
    "results" : [
        {
            "_id" : "A",
            "value" : 3
        },
        {
            "_id" : "B",
            "value" : 7
        },
        {
            "_id" : "C",
            "value" : 11
        }
    ],
    "timeMillis" : 231,
    "counts" : {
        "input" : 6,
        "emit" : 6,
        "reduce" : 3,
        "output" : 3
    },
    "ok" : 1
}

现在,这当然是按照指定的分组键的自然“升序”顺序排序的,并且完全按照合理记录的方式进行排序。此外,考虑到大多数 SQL 存储引擎如何在结果中处理这种聚合工作,那么以有序键的方式返回就“有意义”了。

真的,“这里有什么问题?”。作为回答许多社区问题的频繁贡献者,我“可以说”并且具有相当大的权威,一般期望结果应该像人们一样按“分组键”排序合理预期。此外,还有一些常见用例希望进一步“分析”“系列”中的聚合结果,因为它们应该自然发生。一个常见的情况是“分析每个聚合结果之间的差异”,例如"determining the difference between each average on days"。只是一个例子,但类似的事情经常被问到。

我们中的大多数人(至少有一些经验的人)都非常了解$sort 聚合管道阶段。但我认为,这里真正要问的是“我们为什么要这样做?”。

.mapReduce() 的原始聚合选项就像人们期望的那样。那么为什么.aggregate() 不这样做呢?

这上面有JIRA 吗?有什么实际行动可以解决吗?

当前将$sort 作为附加阶段的解决方法确实非常“粗鲁”,我认为使用该产品的社区应该得到比这更好的东西。任何“聚合”操作的预期行为是“键”应在结果中排​​序。那么为什么我们不能这样做呢?目前正在采取什么措施来解决这个问题?

如果不立即努力,那真是太可惜了,因为这会削弱人们在考虑将 MongoDB 作为其应用程序的存储解决方案时应该涌向的“非常有用的工具”。

我希望我们能朝着更好的方向努力。


再澄清一点。值得注意的是,聚合输出没有特别排序,但它确实出现在密钥的“发现顺序”中,但当然是相反的。举个例子:

db.example.insert([
    { "field": "B", "value": 4 },
    { "field": "A", "value": 1 },
    { "field": "B", "value": 3 },
    { "field": "C", "value": 5 },
    { "field": "A", "value": 2 },
    { "field": "C", "value": 6 }
])

会产生:

{ "_id" : "C", "value" : 11 }
{ "_id" : "A", "value" : 3 }
{ "_id" : "B", "value" : 7 }

所以堆栈总是按照分组键被发现的顺序颠倒。

这就是问题所在,堆栈总是颠倒的,以及为什么 mapReduce 在对分组键进行预排序时采用不同的方法。有什么好处或具体原因吗?或者可以做得更好。

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    我在 MongoDB 版本 3.4.4 上尝试了与您相同的插入并得到与您相同的结果!感兴趣并尝试了更多,如下所述,

    db.example.insert([
        { "field" : "A", "value" : 1 },
        { "field" : "B", "value" : 2 },
        { "field" : "C", "value" : 3 },
        { "field" : "D", "value" : 4 },
        { "field" : "E", "value" : 5 },
        { "field" : "A", "value" : 6 },
        { "field" : "B", "value" : 7 },
        { "field" : "C", "value" : 8 },
        { "field" : "D", "value" : 9 },
        { "field" : "E", "value" : 10 },
    ])
    

    收集到位后,运行相同的聚合操作,如下所示:

    db.example.aggregate([
        { "$group": {
            "_id": "$field",
            "value": { "$sum": "$value" }
        }}
    ])
    

    返回结果如下:

    [
        { "_id" : "E", "value" : 15 },
        { "_id" : "D", "value" : 13 },
        { "_id" : "C", "value" : 11 },
        { "_id" : "A", "value" : 7 },
        { "_id" : "B", "value" : 9 }
    ]
    

    聚合输出似乎没有遵循您提到的顺序,即。发现顺序相反。如果是这样,那么聚合结果不应该遵循 E,D,C,B,A 而不是 E,D,C,A,B 吗?

    这些已被删除并使用不同的文档再次尝试

    db.example.insert([
        { "field" : "A", "value" : 1 },
        { "field" : "B", "value" : 2 },
        { "field" : "C", "value" : 3 },
        { "field" : "D", "value" : 4 },
        { "field" : "E", "value" : 5 },
        { "field" : "E", "value" : 6 },
        { "field" : "D", "value" : 7 },
        { "field" : "C", "value" : 8 },
        { "field" : "B", "value" : 9 },
        { "field" : "A", "value" : 10 },
    ])
    

    像这样运行相同的聚合操作:

    db.example.aggregate([
        { "$group": {
            "_id": "$field",
            "value": { "$sum": "$value" }
        }}
    ])
    

    返回的结果与上面的顺序相同,即。 E、D、C、A、B:

    [
        { "_id" : "E", "value" : 11 },
        { "_id" : "D", "value" : 11 },
        { "_id" : "C", "value" : 11 },
        { "_id" : "A", "value" : 11 },
        { "_id" : "B", "value" : 11 }
    ]
    

    总体而言,在上述场景中,结果中没有“颠倒”的键顺序。

    【讨论】:

      【解决方案2】:

      我真的不认为 MongoDB 默认应该对结果进行排序。如果您需要按特定顺序排列文档,则应始终对它们进行排序。

      通过 .mapReduce() 的原始聚合选项就像人们期望的那样

      我不确定人们是否期望这一点——除非我特别要求,否则我当然不希望事情按特定顺序进行。如果它们恰好按某种顺序排列,那么这是一个实现细节,您通常不应该依赖它。

      如果您认为它有用,请打开一个关于它的 Jira 错误,但我个人认为没有必要。如果用户可以写$group,那么他们也将能够找出$sort

      【讨论】:

      • 这里的部分问题确实提出了是否考虑了 JIRA 并且肯定应该有一个。此外,您似乎基本上认为这是一种“观点”,但您的对立本质上本身就是一种观点。因此,这里实际上没有任何尝试来回答为什么这里的两种聚合方法之间存在差异或应该存在差异的基本问题。这就是问题,答案将解决这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-02
      • 2017-10-30
      • 1970-01-01
      • 1970-01-01
      • 2016-07-25
      • 2014-11-18
      相关资源
      最近更新 更多