使用$ lookup

时间:2017-07-05 04:32:23

标签: mongodb aggregation-framework

我用管道发现了Mongodb及其查询,而且我在案件中挣扎。

我正在寻找每个 pathsList 文档,在那里我可以找到从位置B到位置C的路径

  • 输入:10和12
  • 输出:[pathsList](我希望在这个例子中只有1个结果,但后来更可能是一个数组)

假设我有来自pathsListsCollection的以下2个 pathsList 文档,它们有一个路径文档数组

-------------
pathsList = {
    _id: ObjectId(...),
    pathIds: [path1Id, path2Id, path3Id]
}
-------------
path1 = {
    _id: ObjectId(...),
    positionStart: 8,
    positionFinal: 10,
    index:0
}
-------------
path2 = {
    _id: ObjectId(...),
    positionStart: 10,
    positionFinal: 12,
    index:1
}
-------------
path3 = {
    _id: ObjectId(...),
    positionStart: 12,
    positionFinal: 14,
    index:2
}
-------------

-------------
pathsList = {
    _id: ObjectId(...),
    pathIds: [path4Id, path5Id, path6Id]
}
-------------
path4 = {
    _id: ObjectId(...),
    positionStart: 14,
    positionFinal: 12,
    index:0
}
-------------
path5 = {
    _id: ObjectId(...),
    positionStart: 12,
    positionFinal: 10,
    index:1
}
-------------
path6 = {
    _id: ObjectId(...),
    positionStart: 10,
    positionFinal: 8,
    index:2
}
-------------

到目前为止,我做过类似的事情:

pathsListCollection.aggregate([
    {
    $lookup:{
        from: "pathsCollection",
        localField: "pathIds",
        foreignField: "_id",
        as: paths
        }
    },
    {
        $match:{
            paths.positionStart : 10 // first input value
        }
    },
    {
        $match:{
            paths.positionFinal : 12  // second input value
        }
    },
])

这样做我得到2个路径列表文档。

现在,如何更改此聚合以仅查找具有positionStart = 10和positionFinal = 12的那个特定顺序的聚合 如果第一个表达式已经验证,如何尝试验证第二个表达式?

我在第一场$匹配后尝试切片路径数组$ $ slice,并继续查询其余部分,但无法找到任何明智的语法来执行此操作。

ie:使用第一个路径列表,我使用路径数组达到第一个$匹配:

[{
    _id: ObjectId(...),
    positionStart: 8,
    positionFinal: 10,
    index:0
},
{
    _id: ObjectId(...),
    positionStart: 10,     // first $match here
    positionFinal: 12,
    index:1
},
{
    _id: ObjectId(...),
    positionStart: 12,
    positionFinal: 14,
    index:2
}]

我希望在数组上执行下一个$ match:

[{
    _id: ObjectId(...),
    positionStart: 10,
    positionFinal: 12,    // second $match has to start from this elem of the array
    index:1
},
{
    _id: ObjectId(...),
    positionStart: 12,
    positionFinal: 14,
    index:2
}]

有可能吗? 在第一个地方查询pathsCollection而不是pathListsCollection会更容易吗?

但是,我可以采用任何不同的方法,或者可以帮助我解决这个问题 提前致谢

1 个答案:

答案 0 :(得分:1)

如果我收到了此消息,那么您希望根据此处的匹配positionStartpositionFinish进行“切片”。

最佳案例

如果您确实在"pathsCollection"上启动了查询,那么实际上对性能会更好,因为这是您实际要匹配项目的地方。因此,$match应该首先完成并使用$or表达式在整个范围内“切片”:

然后使用$lookup,您将只返回使用“切片”数组格式化的"pathsListCollection"项:

在您的数据的最小样本上,从“开始”10到“完成”12将是:

db.getCollection('pathsCollection').aggregate([
  { "$match": {
    "positionStart": { "$gte": 10, "$lte": 12 },
    "positionFinal": { "$gte": 10, "$lte": 12 }
  }},
  { "$lookup": {
    "from": "pathsListCollection",
    "localField": "_id",
    "foreignField": "pathIds",
    "as": "pathsList"
  }},
  { "$unwind": "$pathsList" },
  { "$addFields": {
    "pathsList.pathIds": {
      "$filter": {
        "input": "$pathsList.pathIds",
        "as": "p",
        "cond": { "$eq": [ "$_id", "$$p" ] }
      }
    }
  }},
  { "$unwind": "$pathsList.pathIds" },
  { "$group": {
    "_id": "$pathsList._id",
    "pathIds": {
      "$push": {
        "_id": "$_id",
        "positionStart": "$positionStart",
        "positionFinal": "$positionFinal",
        "index": "$index"  
      }  
    }  
  }},
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionStart",
              0 
            ]},
            10
          ]},
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionFinal",
              -1
            ]},
            12
          ]}  
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"  
    }  
  }}
])

会产生:

/* 1 */
{
    "_id" : ObjectId("595db5d8f5f11516540d1185"),
    "pathIds" : [ 
        {
            "_id" : ObjectId("595db4c7f5f11516540d1183"),
            "positionStart" : 10.0,
            "positionFinal" : 12.0,
            "index" : 1.0
        }
    ]
}

将“范围”缩小为“开始”10和“完成”14,即可:

db.getCollection('pathsCollection').aggregate([
  { "$match": {
    "positionStart": { "$gte": 10, "$lte": 14 },
    "positionFinal": { "$gte": 10, "$lte": 14 }
  }},
  { "$lookup": {
    "from": "pathsListCollection",
    "localField": "_id",
    "foreignField": "pathIds",
    "as": "pathsList"
  }},
  { "$unwind": "$pathsList" },
  { "$addFields": {
    "pathsList.pathIds": {
      "$filter": {
        "input": "$pathsList.pathIds",
        "as": "p",
        "cond": { "$eq": [ "$_id", "$$p" ] }
      }
    }
  }},
  { "$unwind": "$pathsList.pathIds" },
  { "$group": {
    "_id": "$pathsList._id",
    "pathIds": {
      "$push": {
        "_id": "$_id",
        "positionStart": "$positionStart",
        "positionFinal": "$positionFinal",
        "index": "$index"  
      }  
    }  
  }},
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionStart",
              0 
            ]},
            10
          ]},
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionFinal",
              -1
            ]},
            14
          ]}  
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"  
    }  
  }}
])

产:

/* 1 */
{
    "_id" : ObjectId("595db5d8f5f11516540d1185"),
    "pathIds" : [ 
        {
            "_id" : ObjectId("595db4c7f5f11516540d1183"),
            "positionStart" : 10.0,
            "positionFinal" : 12.0,
            "index" : 1.0
        }, 
        {
            "_id" : ObjectId("595db4c7f5f11516540d1184"),
            "positionStart" : 12.0,
            "positionFinal" : 14.0,
            "index" : 2.0
        }
    ]
}

反向案例

语法可能看起来有点短,但它可能不是性能最佳的选项,因为在实际执行$lookup之前你无法“反向”查询"pathsCollection"

db.pathsListCollection.aggregate([
  { "$lookup": {
    "from": "pathsCollection",
    "localField": "pathIds",
    "foreignField": "_id",
    "as": "pathIds"    
  }},
  { "$unwind": "$pathIds" },
  { "$match": {
    "pathIds.positionStart": { "$gte": 10, "$lte": 14 },
    "pathIds.positionFinal": { "$gte": 10, "$lte": 14 }
  }},
  { "$group": {
    "_id": "$_id",
    "pathIds": { "$push": "$pathIds" }    
  }},
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionStart",
              0 
            ]},
            10
          ]},
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionFinal",
              -1
            ]},
            14
          ]}  
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"  
    }  
  }}
])

这是关于MongoDB在应用于服务器时实际发布$lookup的方式的“反向案例”的最佳形式,可以在“explain”输出中看到:

    {
        "$lookup" : {
            "from" : "pathsCollection",
            "as" : "pathIds",
            "localField" : "pathIds",
            "foreignField" : "_id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "$and" : [ 
                    {
                        "positionStart" : {
                            "$gte" : 10.0
                        }
                    }, 
                    {
                        "positionStart" : {
                            "$lte" : 14.0
                        }
                    }, 
                    {
                        "positionFinal" : {
                            "$gte" : 10.0
                        }
                    }, 
                    {
                        "positionFinal" : {
                            "$lte" : 14.0
                        }
                    }
                ]
            }
        }
    }, 
    {
        "$group" : {

显示$unwind$match“神奇地”消失了。它们现在当然已经“汇总”到$lookup中,以便在查询相关数据时,实际上只有 那些符合给定条件的结果。

“非最佳”方法将改为$filter。但实际上返回了来自相关集合的 ALL 结果,然后只有在“完整”数组已经存在时才会删除:

db.pathsListCollection.aggregate([
  { "$lookup": {
    "from": "pathsCollection",
    "localField": "pathIds",
    "foreignField": "_id",
    "as": "pathIds"    
  }},
  { "$addFields": {
    "pathIds": {
      "$filter": {
        "input": "$pathIds",
        "as": "p",
        "cond": {
          "$and": [
            { "$gte": [ "$$p.positionStart", 10 ] },
            { "$lte": [ "$$p.positionStart", 14 ] },            
            { "$gte": [ "$$p.positionFinal", 10 ] },
            { "$lte": [ "$$p.positionFinal", 14 ] },            
          ]
        }
      } 
    }  
  }},
  { "$match": {
    "pathIds": {
      "$elemMatch": {
        "positionStart": { "$gte": 10, "$lte": 14 },
        "positionFinal": { "$gte": 10, "$lte": 14 }
      }
    }
  }},
  { "$redact": {
    "$cond": {
      "if": {
        "$and": [
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionStart",
              0 
            ]},
            10
          ]},
          { "$eq": [
            { "$arrayElemAt": [
              "$pathIds.positionFinal",
              -1
            ]},
            14
          ]}  
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"  
    }  
  }}
])

同时注意到你仍然需要$match$redact,因为结果数组条目仍然满足这种情况下的条件,并且数组实际上并不是“空”的结果$filter

使用的样本

<强> pathsCollection

/* 1 */
{
    "_id" : ObjectId("595db4c7f5f11516540d1182"),
    "positionStart" : 8.0,
    "positionFinal" : 10.0,
    "index" : 0.0
}

/* 2 */
{
    "_id" : ObjectId("595db4c7f5f11516540d1183"),
    "positionStart" : 10.0,
    "positionFinal" : 12.0,
    "index" : 1.0
}

/* 3 */
{
    "_id" : ObjectId("595db4c7f5f11516540d1184"),
    "positionStart" : 12.0,
    "positionFinal" : 14.0,
    "index" : 2.0
}

/* 4 */
{
    "_id" : ObjectId("595db616f5f11516540d1186"),
    "positionStart" : 14.0,
    "positionFinal" : 12.0,
    "index" : 0.0
}

/* 5 */
{
    "_id" : ObjectId("595db616f5f11516540d1187"),
    "positionStart" : 12.0,
    "positionFinal" : 10.0,
    "index" : 1.0
}

/* 6 */
{
    "_id" : ObjectId("595db616f5f11516540d1188"),
    "positionStart" : 10.0,
    "positionFinal" : 8.0,
    "index" : 2.0
}

<强> pathsListCollection

/* 1 */
{
    "_id" : ObjectId("595db5d8f5f11516540d1185"),
    "pathIds" : [ 
        ObjectId("595db4c7f5f11516540d1182"), 
        ObjectId("595db4c7f5f11516540d1183"), 
        ObjectId("595db4c7f5f11516540d1184")
    ]
}

/* 2 */
{
    "_id" : ObjectId("595db62df5f11516540d1189"),
    "pathIds" : [ 
        ObjectId("595db616f5f11516540d1186"), 
        ObjectId("595db616f5f11516540d1187"), 
        ObjectId("595db616f5f11516540d1188")
    ]
}