为了账号安全,请及时绑定邮箱和手机立即绑定

$ lookup之后的聚合过滤器

$ lookup之后的聚合过滤器

Cats萌萌 2019-09-24 16:23:38
如何在$ 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 ]    }  ]}
查看完整描述

3 回答

?
绝地无双

TA贡献1946条经验 获得超4个赞

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

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匹配管道中文档的总大小超出了所有文档的最大文档大小,以了解为什么这是通常必需的并且是最佳方法。


对于MongoDB 3.6及更高版本,通常要在将所有内容都返回到数组之前“过滤”外部集合的结果,来表达更具表现力的“子管道”。


回到答案,尽管实际上描述了为什么所提问题根本不需要“加入”。


原版的

$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它本身不能“还”查询以仅选择某些数据。因此,所有“过滤”操作都需要在$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。


您在这里似乎还缺少的是,您要查找的结果根本不需要聚合或“子查询”。您已经结束或可能在其他地方找到的结构是“设计的”,以便您可以在单个查询请求中获得“节点”及其所有“子代”。


这意味着只有“ query”才是真正需要的,而数据收集(由于没有真正“减少”任何内容而已)的全部工作只是迭代游标结果的功能:


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

      ]

    }

  ]

}

并证明您在这里真正需要做的就是发出“单个”查询以选择父项和子项。返回的数据是相同的,并且您在服务器或客户端上所做的所有工作都在“按摩”为另一种收集的格式。


这是在这种情况下您可以“思考”如何在“关系”数据库中做事的情况之一,而没有意识到由于数据的存储方式已“改变”,因此您不再需要使用同样的方法。


这正是文档示例“带有子引用的模型树结构”结构中的要点,在该示例中可以轻松地在一个查询中选择父级和子级。


查看完整回答
反对 回复 2019-09-24
?
长风秋雁

TA贡献1757条经验 获得超7个赞

就“性能”而言$lookup,实际上是在“服务器”上进行“两个”查询。“客户端”可以执行“两个”查询,但是当然会涉及网络和响应开销,这将减慢该过程。因此,为什么要这么做$lookup。但是我也是说您的案子不需要这个。所有项目已经在同一个集合中,因此只需简单地选择它们并$group相应地选择它们即可。就“性能”而言,到目前为止,这是三个中更好的选择。

查看完整回答
反对 回复 2019-09-24
?
慕姐4208626

TA贡献1852条经验 获得超7个赞

我认为您仍然停留在“关系思考”中,这使您在实际上不需要“子查询”或“自我加入”的情况下也不需要。在此处添加了纯“无聚合”方法来说明这一点。

查看完整回答
反对 回复 2019-09-24
  • 3 回答
  • 0 关注
  • 990 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信