如何在$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 ] }

我 Select 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 ]
    }
  ]
}

推荐答案

这里的问题实际上是关于一些不同的东西,根本不需要$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 coalisement

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,了解为什么这通常是必要的,而且是更优化的.

对于MongoDB 3.6及以后的版本,更具表现力的"子管道"通常是在任何东西返回到数组之前,您想要"过滤"外部集合的结果.

回到答案上来,它实际上描述了为什么问题需要"不加入"的原因....


Original

像这样使用$lookup并不是做你想做的事情的最"有效"的方式.但稍后会有更多关于这方面的内容.

作为一个基本概念,只需在生成的数组中使用$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本身不能"还"查询,只 Select 某些数据.所以所有的"过滤"都需要在$lookup次之后进行

但对于这种类型的"自连接",最好不要使用$lookup,完全避免额外读取和"哈希合并"的开销.取而代之的是获取相关项目和$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 ]
    }
  ]
}

所以这里唯一真正的问题是从数组中"过滤"任何null个结果,当当前文档是parent个正在处理的项到$push个时创建的.


这里您似乎还缺少的是,您所寻找的结果根本不需要聚合或"子查询".您已经总结或可能在其他地方找到的 struct 是"设计"的,这样您就可以在一个查询请求中获得一个" node "及其所有"子 node ".

这意味着真正需要的只是"查询",而数据收集(这是所有正在发生的事情,因为没有真正"减少"任何内容)只是一个迭代光标结果的函数:

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
      ]
    }
  ]
}

并证明您在这里真正需要做的就是发出"单一"查询来 Select 父项和子项.返回的数据完全相同,您在服务器或客户机上所做的一切都是"按摩"成另一种收集的格式.

在这种情况下,您可能会"陷入"思考如何在"关系"数据库中进行操作,而没有意识到由于数据的存储方式已"改变",因此不再需要使用相同的方法.

这正是文档示例"Model Tree Structures with Child References"在其 struct 中的意义所在,它使得在一个查询中 Select 父项和子项变得容易.

Mongodb相关问答推荐

MongoDB.ArrayFilters出错:在路径中找不到标识符';elem';的数组筛选器

在MondoDB中:将对象数组从切片索引数组切片,并通过聚合推入数组

获取文档字段名并将其作为嵌套字段添加到聚合中

定期自动轮换 MongoDb 集合

如何在 go mongo-driver 中为 options.FindOne() 设置限制

根据条件删除一些数组元素并将数组的大小更新为mongo中的另一个文件

Mongodb按值分组并计算出现次数

mongo.lock 文件有什么用?

Spring Boot MongoDB 连接问题

MongoDB 存储 ObjectId 的数组

MongoDB $or 查询

Mongoose.js:嵌套属性的原子更新?

Spring Data MongoDB 中的独特之处

在MongoDB中查询一个半​​径内的位置

mongodb类型更改为数组

mongodb:upserting:仅在插入文档时设置值

使用自定义 _id 值时 mongodb 中的 Upserts

在 MongoDB 中为现有用户更改密码

如何判断 MongoDB 中是否存在字段?

MongoDB InsertMany 与 BulkWrite