聚合 $lookup 不返回元素原始数组顺序

Luc*_*elo 10 mongodb mongodb-query aggregation-framework

查询返回元素在其集合中的放置顺序,忽略初始数组的顺序。这会影响我们系统的功能。是否有任何额外的命令可以将其按正确的顺序排列?有没有可用的解决方法?

下面是一个简单的例子:

Collection1 文档

{
  "_id":ObjectId("5c781752176c512f180048e3"),
  "Name":"Pedro",
  "Classes":[
    {"ID": ObjectId("5c7af2b2f6f6e47c9060d7ce") },
    {"ID": ObjectId("5c7af2bcf6f6e47c9060d7cf") },
    {"ID": ObjectId("5c7af2aaf6f6e47c9060d7cd") }
  ]
}
Run Code Online (Sandbox Code Playgroud)

Collection2 文件

{
  "_id":ObjectId("5c7af2aaf6f6e47c9060d7cd"),
  "variable1":"A"
},

{
  "_id": ObjectId("5c7af2b2f6f6e47c9060d7ce"),
  "variable1":"B"
},

{
  "_id": ObjectId("5c7af2bcf6f6e47c9060d7cf"),
  "variable1":"C"
}
Run Code Online (Sandbox Code Playgroud)

查询:

aggregate(
  pipeline = '[
  {"$match": {"_id": {"$oid": "5c781752176c512f180048e3"}}},
  {"$lookup": {"from": "collection2", "localField": "Classes.ID", "foreignField": "_id", "as": "Collection2_doc"}}
  ]'
)
Run Code Online (Sandbox Code Playgroud)

返回:

结果顺序:

[
    {
      "_id":ObjectId("5c7af2aaf6f6e47c9060d7cd"),
      "variable1":"A"
    },
    {
      "_id": ObjectId("5c7af2b2f6f6e47c9060d7ce"),
      "variable1":"B"
    },
    {
      "_id": ObjectId("5c7af2bcf6f6e47c9060d7cf"),
      "variable1":"C"
    }
]
Run Code Online (Sandbox Code Playgroud)

预期顺序(第一个文档数组顺序):

[
    {
      "_id": ObjectId("5c7af2b2f6f6e47c9060d7ce"),
      "variable1":"B"
    },
    {
      "_id": ObjectId("5c7af2bcf6f6e47c9060d7cf"),
      "variable1":"C"
    },
    {
      "_id":ObjectId("5c7af2aaf6f6e47c9060d7cd"),
      "variable1":"A"
    }
]
Run Code Online (Sandbox Code Playgroud)

是否有任何额外的命令 ex. $sort可以用来返回它尊重原始数组顺序?

Nei*_*unn 22

这是实现的“设计” $lookup。什么实际发生的“引擎盖下”是MongoDB的internall转换中的参数$lookup,以新的表现力使用的格式$expr$in。即使在实现这种表达形式之前的版本中,“值数组”的内部机制也非常相似。

这里的解决方案是保留原始数组的副本作为重新排序“连接”项的参考:

collection.aggregate([
  {"$match": {"_id": ObjectId("5c781752176c512f180048e3") }},
  {"$lookup": {
    "from": "collection2",
    "let": { "classIds": "$Classes.ID" },
    "pipeline": [
      { "$match": {
        "$expr": { "$in": [ "$_id", "$$classIds" ] }
      }},
      { "$addFields": {
        "sort": {
          "$indexOfArray": [ "$$classIds", "$_id" ]
        }
      }},
      { "$sort": { "sort": 1 } },
      { "$addFields": { "sort": "$$REMOVE" }}
    ],
    "as": "results"
  }}
])
Run Code Online (Sandbox Code Playgroud)

或者按照传统$lookup用法:

collection.aggregate([
  {"$match": {"_id": ObjectId("5c781752176c512f180048e3") }},
  {"$lookup": {
    "from": "collection2",
    "localField": "Classes.ID",
    "foreignField": "_id",
    "as": "results"
  }},
  { "$unwind": "$results" },
  { "$addFields": {
    "sort": {
      "$indexOfArray": [ "$Classes.ID", "$results._id" ]
    }
  }},
  { "$sort": { "_id": 1, "sort": 1 } },
  { "$group": {
    "_id": "$_id",
    "Name": { "$first": "$Name" },
    "Classes": { "$first": "$Classes" },
    "results": { "$push": "$results" }
  }}
])
Run Code Online (Sandbox Code Playgroud)

两种变体产生相同的输出:

{
        "_id" : ObjectId("5c781752176c512f180048e3"),
        "Name" : "Pedro",
        "Classes" : [
                {
                        "ID" : ObjectId("5c7af2b2f6f6e47c9060d7ce")
                },
                {
                        "ID" : ObjectId("5c7af2bcf6f6e47c9060d7cf")
                },
                {
                        "ID" : ObjectId("5c7af2aaf6f6e47c9060d7cd")
                }
        ],
        "results" : [
                {
                        "_id" : ObjectId("5c7af2b2f6f6e47c9060d7ce"),
                        "variable1" : "B"
                },
                {
                        "_id" : ObjectId("5c7af2bcf6f6e47c9060d7cf"),
                        "variable1" : "C"
                },
                {
                        "_id" : ObjectId("5c7af2aaf6f6e47c9060d7cd"),
                        "variable1" : "A"
                }
        ]
}
Run Code Online (Sandbox Code Playgroud)

一般概念是$indexOfArray_id来自“连接”内容的值进行比较,以找到它在原始源数组中的“索引”位置"$Classes.ID"。不同的$lookup语法变体对于如何访问此副本以及如何基本重建有不同的方法。

$sort任一被当然设置实际文件的顺序,所述流水线处理内部的表现形式,或经由的暴露文件$unwind。您使用的地方$unwind然后将$group返回到原始文档形式。

注意:此处的使用示例$indexOfArray至少依赖于 MongoDB 3.4,并且$$REMOVE与 MongoDB 3.6 一致,表达方式也一样 $lookup

还有其他方法可以为以前的版本重新排序数组,但这些方法在MongoDB 的 $in 子句保证顺序中有更详细的演示。实际上,您目前应该作为生产 MongoDB 版本运行的最低限度是 3.4 版本。

有关受支持版本和结束日期的完整详细信息,请参阅MongoDB 服务器下的支持政策

  • @AsyaKamsky 你能用上面的例子提供答案吗? (2认同)