【问题标题】:MongoDB sort vs aggregate $sort on array indexMongoDB 排序与数组索引上的聚合 $sort
【发布时间】:2015-09-02 08:16:46
【问题描述】:

使用包含以下文档的 MongoDB 集合 test

{ "_id" : 1, "color" : "blue", "items" : [  1,  2,  0 ] }
{ "_id" : 2, "color" : "red", "items" : [  0,  3,  4 ] }

如果我根据items 数组中的第二个元素以相反的顺序对它们进行排序,则使用

db.test.find().sort({"items.1": -1})

它们将被正确排序为:

{ "_id" : 2, "color" : "red", "items" : [  0,  3,  4 ] }
{ "_id" : 1, "color" : "blue", "items" : [  1,  2,  0 ] }

但是,当我尝试使用 aggregate 函数对它们进行排序时:

db.test.aggregate([{$sort: {"items.1": -1} }])

即使查询被接受为有效,它们也不会正确排序:

{
    "result" : [
        {
            "_id" : 1,
            "color" : "blue",
            "items" : [
                1,
                2,
                0
            ]
        },
        {
            "_id" : 2,
            "color" : "red",
            "items" : [
                0,
                3,
                4
            ]
        }
    ],
    "ok" : 1
}

这是为什么?

【问题讨论】:

    标签: mongodb sorting mongodb-query aggregation-framework


    【解决方案1】:

    聚合框架只是不以与通常应用于.find() 查询相同的方式“处理”数组。这不仅适用于像 .sort() 这样的操作,而且适用于其他操作,即 $slice,尽管该示例即将得到修复(稍后会详细介绍)。

    因此,几乎不可能使用“点符号”形式处理任何事情,并像您一样使用数组位置的索引。但是有办法解决这个问题。

    你“可以”做的基本上是计算出“第n个”数组元素实际上是一个值,然后将其作为一个可以排序的字段返回:

      db.test.aggregate([
        { "$unwind": "$items" },
        { "$group": { 
          "_id": "$_id",
          "items": { "$push": "$items" },
          "itemsCopy":  { "$push": "$items" },
          "first": { "$first": "$items" }
        }},
        { "$unwind": "$itemsCopy" },
        { "$project": {
          "items": 1,
          "itemsCopy": 1,
          "first": 1,
          "seen": { "$eq": [ "$itemsCopy", "$first" ] }
        }},
        { "$match": { "seen": false } },
        { "$group": {
          "_id": "$_id",
          "items": { "$first": "$items" },
          "itemsCopy": { "$push": "$itemsCopy" },
          "first": { "$first": "$first" },
          "second": { "$first": "$itemsCopy" }
        }},
        { "$sort": { "second": -1 } }
      ])
    

    这是一种可怕且“可迭代”的方法,在使用$unwind 处理后,通过从数组中获取每个文档的$first 匹配项,您基本上可以“逐步执行”每个数组元素。然后在再次$unwind 之后,您测试该数组元素是否与已从识别的数组位置“看到”的元素相同。

    这很糟糕,而且你想要移动的位置越多越糟糕,但它确实得到了结果:

    { "_id" : 2, "items" : [ 0, 3, 4 ], "itemsCopy" : [ 3, 4 ], "first" : 0, "second" : 3 }
    { "_id" : 1, "items" : [ 1, 2, 0 ], "itemsCopy" : [ 2, 0 ], "first" : 1, "second" : 2 }
    { "_id" : 3, "items" : [ 2, 1, 5 ], "itemsCopy" : [ 1, 5 ], "first" : 2, "second" : 1 }
    

    幸运的是,即将发布的 MongoDB(目前在开发版本中可用)对此进行了“修复”。它可能不是您想要的“完美”修复,但它确实解决了基本问题。

    那里有一个新的$slice 运算符可用于聚合框架,它将从索引位置返回数组的所需元素:

      db.test.aggregate([
        { "$project": {
          "items": 1,
          "slice": { "$slice": [ "$items",1,1 ] }
        }},
        { "$sort": { "slice": -1 } }
      ])
    

    产生:

    { "_id" : 2, "items" : [ 0, 3, 4 ], "slice" : [ 3 ] }
    { "_id" : 1, "items" : [ 1, 2, 0 ], "slice" : [ 2 ] }
    { "_id" : 3, "items" : [ 2, 1, 5 ], "slice" : [ 1 ] }
    

    所以你可以注意到,作为一个“切片”,结果仍然是一个“数组”,但是聚合框架中的$sort一直使用数组的“第一个位置”来对内容进行排序。这意味着使用从索引位置提取的奇异值(就像上面的长过程一样),结果将按照您的预期进行排序。

    这里的最终情况就是它的工作原理。要么接受上面需要的操作来处理数组的索引位置,要么“等待”直到一个全新的闪亮版本通过更好的操作符来拯救你。

    【讨论】:

    • 哇,非常感谢您的详尽回答!真的很感激。
    • 详细回答了 stackoverflow 中的预期。谢谢
    猜你喜欢
    • 2021-01-15
    • 1970-01-01
    • 2015-12-28
    • 2015-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-14
    相关资源
    最近更新 更多