$ lookup后的聚合过滤器

时间:2016-04-06 18:55:51

标签: javascript mongodb mongodb-query aggregation-framework

如何在$ lookup之后添加过滤器,还是有其他方法可以执行此操作?

我的数据收集测试是:

{ "_id" : ObjectId("570557d4094a4514fc1291d6"), "id" : 100, "value" : "0", "contain" : [ ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d7"), "id" : 110, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d8"), "id" : 120, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d9"), "id" : 121, "value" : "2", "contain" : [ 100, 120 ] }

我选择id 100并聚合孩子:

db.test.aggregate([ {
  $match : {
    id: 100
  }
}, {
  $lookup : {
    from : "test",
    localField : "id",
    foreignField : "contain",
    as : "childs"
  }
}]);

我回来了:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d9"),
      "id":121,
      "value":"2",
      "contain":[ 100, 120 ]
    }
  ]
}

但我只想要与"值匹配的孩子:1"

最后我期待这个结果:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}

1 个答案:

答案 0 :(得分:55)

这里的问题实际上是关于不同的东西,根本不需要$lookup。但对于任何到达这里的人来说,纯粹来自"在$ lookup"之后进行过滤。那么这些技巧适合你:

MongoDB 3.6 - 子管道

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
      "from": "test",
      "let": { "id": "$id" },
      "pipeline": [
        { "$match": {
          "value": "1",
          "$expr": { "$in": [ "$$id", "$contain" ] }
        }}
      ],
      "as": "childs"
    }}
])

早些时候 - $ lookup + $ unwind + $ match coalescence

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$unwind": "$childs" },
    { "$match": { "childs.value": "1" } },
    { "$group": {
        "_id": "$_id",
        "id": { "$first": "$id" },
        "value": { "$first": "$value" },
        "contain": { "$first": "$contain" },
        "childs": { "$push": "$childs" }
     }}
])

如果你质疑为什么你会$unwind而不是在数组上使用$filter,那么请阅读Aggregate $lookup Total size of documents in matching pipeline exceeds maximum document size,了解为什么这通常是必要的并且更加优化。< / p>

对于MongoDB 3.6及更高版本的版本,那么更具表现力的&#34;子管道&#34;通常是你想要的&#34;过滤&#34;在将任何内容返回到数组之前,外部集合的结果。

回到答案,虽然它实际上描述了为什么问题需要&#34;没有加入&#34;一点......

原始

使用$lookup这样的效率并不是最高效的&#34;在这里做你想做的事。但是稍后会更多。

作为一个基本概念,只需在结果数组上使用$filter

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$project": {
        "id": 1,
        "value": 1,
        "contain": 1,
        "childs": {
           "$filter": {
               "input": "$childs",
               "as": "child",
               "cond": { "$eq": [ "$$child.value", "1" ] }
           }
        }
    }}
]);

或者改为使用$redact

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$redact": {
        "$cond": {
           "if": {
              "$or": [
                { "$eq": [ "$value", "0" ] },
                { "$eq": [ "$value", "1" ] }
              ]
           },
           "then": "$$DESCEND",
           "else": "$$PRUNE"
        }
    }}
]);

两者都得到相同的结果:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}

底线是$lookup本身不能&#34;然而&#34;查询只选择某些数据。所以&#34;过滤&#34;需要在$lookup

之后发生

但真的对于这种类型的&#34;自我加入&#34;你最好不要使用$lookup并避免额外读取的开销和#34;哈希合并&#34;完全。只需获取相关项目,然后$group

db.test.aggregate([
  { "$match": { 
    "$or": [
      { "id": 100 },
      { "contain.0": 100, "value": "1" }
    ]
  }},
  { "$group": {
    "_id": {
      "$cond": {
        "if": { "$eq": [ "$value", "0" ] },
        "then": "$id",
        "else": { "$arrayElemAt": [ "$contain", 0 ] }
      }
    },
    "value": { "$first": { "$literal": "0"} },
    "childs": {
      "$push": {
        "$cond": {
          "if": { "$ne": [ "$value", "0" ] },
          "then": "$$ROOT",
          "else": null
        }
      }
    }
  }},
  { "$project": {
    "value": 1,
    "childs": {
      "$filter": {
        "input": "$childs",
        "as": "child",
        "cond": { "$ne": [ "$$child", null ] }
      }
    }
  }}
])

这只是有点不同,因为我故意删除了无关的字段。如果你真的想要自己添加它们:

{
  "_id" : 100,
  "value" : "0",
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [ 100 ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [ 100 ]
    }
  ]
}

所以这里唯一真正的问题是&#34;过滤&#34;数组中的任何null结果,在处理项目$push时当前文档为parent时创建。

您在这里似乎也缺少的是您要查找的结果不需要聚合或&#34;子查询&#34;一点都不你已经在其他地方找到或可能找到的结构是&#34;设计&#34;这样你就可以获得一个节点&#34;以及所有这些&#34;孩子&#34;在单个查询请求中。

这意味着只是&#34;查询&#34;是所有真正需要的东西,数据收集(这是因为没有内容实际上正在发生的所有事情&#34;简化&#34;)只是迭代游标结果的函数:

var result = {};

db.test.find({
  "$or": [
    { "id": 100 },
    { "contain.0": 100, "value": "1" }
  ]
}).sort({ "contain.0": 1 }).forEach(function(doc) {
  if ( doc.id == 100 ) {
    result = doc;
    result.childs = []
  } else {
    result.childs.push(doc)
  }
})

printjson(result);

这完全相同:

{
  "_id" : ObjectId("570557d4094a4514fc1291d6"),
  "id" : 100,
  "value" : "0",
  "contain" : [ ],
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [
              100
      ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [
              100
      ]
    }
  ]
}

并且证明你在这里真正需要做的就是发出&#34;单身&#34;查询以选择父项和子项。返回的数据是一样的,你在服务器或客户端上所做的只是&#34;按摩&#34;另一种收集的格式。

这是你可以赶上的案例之一&#34;思考你是如何在一个关系中做事的。数据库,并没有意识到,由于数据的存储方式已经改变了#34;,您不再需要使用相同的方法。

这正是文档示例"Model Tree Structures with Child References"在其结构中的重点,它可以在一个查询中轻松选择父项和子项。