使用带条件的 $graphLookup

use*_*001 6 mongodb mongodb-query aggregation-framework

我试图找到给定孩子的祖先。在某个时候,那个孩子的祖先姓氏改变了。我想用给定的姓氏找到这个孩子的最后一个父母。例如:

{
    "_id":1,
    "parent":null
    "first":"Bob",
    "last":"Sagget"
},
{
    "_id":2,
    "parent":1,
    "first":"Jane",
    "last":"Dor"
},
{
    "_id":3,
    "parent":2,
    "first":"Crane",
    "last":"Dor"
},
{
    "_id":4,
    "parent":3,
    "first":"Ho",
    "last":"Dor"
},
{
    "_id":5,
    "parent":4,
    "first":"Mor",
    "last":"Dor"
}
Run Code Online (Sandbox Code Playgroud)

我想查询_id 5并获取姓氏Dor的祖先。在这个数据库中会有其他我不想看到的名字叫 Dor 的人,所以我不能只查询“Dor”的姓氏。

这是我当前的聚合查询 - 这让我的每个祖先一直到_id1。我怎样才能在_id2处停止?:

db.PeopleDb.aggregate(
[
    {
        $graphLookup: {
                "from": "PeopleDb", 
                "startWith": "$parent", 
                "connectFromField": "parent", 
                "connectToField": "_id", 
                "as": "linearAncestors"
            }
        },
        {
        $match: {
        "_id":5
        }
    },
]);
Run Code Online (Sandbox Code Playgroud)

Nei*_*unn 12

您正在寻找的基本查询实际上是“开始”,限制只匹配单数文档,_id: 5然后执行$graphLookup以查找祖先。

至于“条件”,涉及一些步骤,因此最好走一遍整个过程并了解发生了什么:

db.PeopleDb.aggregate([
  { "$match": { "_id": 5 } },
  { "$graphLookup": {
    "from": "PeopleDb",
    "startWith": "$parent",
    "connectFromField": "parent",
    "connectToField": "_id",
    "as": "people",
    "depthField": "depth"
  }}
])
Run Code Online (Sandbox Code Playgroud)

然后返回将 连接parent到 的所有递归链_id,还要注意“可选”"depthField"设置以在返回的结果中包含匹配的实际“深度”:

{
    "_id" : 5,
    "parent" : 4,
    "first" : "Mor",
    "last" : "Dor",
    "people" : [
        {
            "_id" : 1,
            "parent" : null,
            "first" : "Bob",
            "last" : "Sagget",
            "depth" : NumberLong(3)
        },
        {
            "_id" : 2,
            "parent" : 1,
            "first" : "Jane",
            "last" : "Dor",
            "depth" : NumberLong(2)
        },
        {
            "_id" : 3,
            "parent" : 2,
            "first" : "Crane",
            "last" : "Dor",
            "depth" : NumberLong(1)
        },
        {
            "_id" : 4,
            "parent" : 3,
            "first" : "Ho",
            "last" : "Dor",
            "depth" : NumberLong(0)
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

因此请注意,4返回是因为它是初始文档3的父级,然后2是父级,然后是父级,依此类推。

您可以使用“可选”"maxDepth"参数将匹配的“深度”限制到管道阶段,或者在“排除 ROOT”元素的特定情况下,您可以使用该"restrictSearchWithMatch"选项简单地排除带有null父级的结果:

db.PeopleDb.aggregate([
  { "$match": { "_id": 5 } },
  { "$graphLookup": {
    "from": "PeopleDb",
    "startWith": "$parent",
    "connectFromField": "parent",
    "connectToField": "_id",
    "as": "people",
    "depthField": "depth",
    "restrictSearchWithMatch": { "parent": { "$ne": null } }
  }}
])
Run Code Online (Sandbox Code Playgroud)

返回相同的结果,但不包括"parent"字段为的“ROOT”文档null

{
    "_id" : 5,
    "parent" : 4,
    "first" : "Mor",
    "last" : "Dor",
    "people" : [
        {
            "_id" : 2,
            "parent" : 1,
            "first" : "Jane",
            "last" : "Dor",
            "depth" : NumberLong(2)
        },
        {
            "_id" : 3,
            "parent" : 2,
            "first" : "Crane",
            "last" : "Dor",
            "depth" : NumberLong(1)
        },
        {
            "_id" : 4,
            "parent" : 3,
            "first" : "Ho",
            "last" : "Dor",
            "depth" : NumberLong(0)
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

当然,同样的原则适用于您的"last"条件,它只能匹配该条件为真的文档。在这里,我将显示这两个条件,但这$or是可选的:

db.PeopleDb.aggregate([
  { "$match": { "_id": 5 } },
  { "$graphLookup": {
    "from": "PeopleDb",
    "startWith": "$parent",
    "connectFromField": "parent",
    "connectToField": "_id",
    "as": "people",
    "depthField": "depth",
    "restrictSearchWithMatch": {
      "$or": [
        { "parent": { "$ne": null }},
        { "last": "Dor" }
      ]
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

但是请注意,这"restrictSearchWithMatch"是一个“递归”条件,因此如果链中的任何“祖先”不满足"last"条件,那么链就会被破坏,并且不会检索进一步的祖先。为了获得“所有祖先”但只显示那些匹配的人,"last"然后你$filter得到结果数组内容:

db.PeopleDb.aggregate([
  { "$match": { "_id": 5 } },
  { "$graphLookup": {
    "from": "PeopleDb",
    "startWith": "$parent",
    "connectFromField": "parent",
    "connectToField": "_id",
    "as": "people",
    "depthField": "depth",
  }},
  { "$addFields": {
    "people": {
      "$filter": {
        "input": "$people",
        "cond": { "$eq": [ "$$this.last", "Dor" ] }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

或者实际上“动态”通过使用字段值表达式而不是硬编码值与“祖先”相关的初始匹配文档的值进行比较:

db.PeopleDb.aggregate([
  { "$match": { "_id": 5 } },
  { "$graphLookup": {
    "from": "PeopleDb",
    "startWith": "$parent",
    "connectFromField": "parent",
    "connectToField": "_id",
    "as": "people",
    "depthField": "depth",
  }},
  { "$addFields": {
    "people": {
      "$filter": {
        "input": "$people",
        "cond": { "$eq": [ "$$this.last", "$last" ] }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

在这种情况下,它是相同的2,3,4祖先,但如果说祖先3实际上具有不同的值,"last"则使用$filter实际返回2,4"restrictSearchWithMatch"只会返回,4因为它3会“破坏链条”。这是主要的区别。

注意,您还不能使用的一件事$graphLookup是不允许使用“字段比较表达式”。如果您想要这些方面的东西,那么您将使用$filter其他操作和可能的其他操作进行进一步的操作,例如,$indexOfArray如果您的实际意图确实是在递归搜索中未满足“字段比较”的点上“打破链”。