MongoDB排序与聚合$ sort对数组索引

时间:2015-09-02 08:16:46

标签: mongodb sorting mongodb-query aggregation-framework

使用包含以下文档的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
}

为什么会这样?

1 个答案:

答案 0 :(得分: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 } }
  ])

这是一种可怕且“可迭代”的方法,在使用$first处理后,通过从数组中获取每个文档的$unwind匹配,您基本上“逐步”遍历每个数组元素。然后再次$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总是使用数组的“第一个位置”来排序内容。这意味着,从索引位置提取的奇异值(就像上面的长程序一样),结果将按照您的预期进行排序。

这里的最终案例就是它的工作原理。您可以使用上面需要的操作来处理阵列的索引位置,或者“等待”直到全新的闪亮版本与更好的操作员一起解决。