如何使用条件加入另外两个集合

Ann*_*nne 4 mongodb mongodb-query aggregation-framework

select tb1.*,tb3 from tb1,tb2,tb3
 where tb1.id=tb2.profile_id and tb2.field='<text>'
 and tb3.user_id = tb2.id and tb3.status =0
Run Code Online (Sandbox Code Playgroud)

实际上我将sql转换为mongosql,如下所示

mongo 我用过的sql

db.getCollection('tb1').aggregate
([
  { $lookup: 
           { from: 'tb2', 
             localField: 'id', 
             foreignField: 'profile_id', 
             as: 'tb2detail' 
            } 
   },

   { $lookup: 
            { from: 'tb3', 
              localField: 'tb2.id', 
              foreignField: 'user_id', 
              as: 'tb3details' 
            } 
    },

{ $match: 
        { 'status': 
                  { '$ne': 'closed' 
                  }, 
          'tb2.profile_type': 'agent', 
          'tb3.status': 0 
         } 
}

])
Run Code Online (Sandbox Code Playgroud)

但没有达到预期的结果..

任何帮助将不胜感激..

Nei*_*unn 11

你在这里缺少的是在它的参数$lookup指定的输出字段中产生一个"数组" as.这是MongoDB"关系"的一般概念,因为文档之间的"关系"表示为文档本身"内部"的"子属性",对于许多人来说是单数或"数组".

由于MongoDB是"无模式的",因此一般的假设$lookup是你的意思是"很多",因此结果"总是"一个数组.因此,寻找"与SQL相同的结果",然后你需要在$unwind那之后的那个数组$lookup.无论是"一个"还是"多个"都无关紧要,因为它仍然"总是"一个数组:

db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])
Run Code Online (Sandbox Code Playgroud)

在这里,您需要注意翻译中缺少的其他内容:

顺序是"重要的"

聚合管道比SQL更"简洁".事实上,它们最好被视为应用于数据源的"一系列步骤",以便整理和转换数据.最好的模拟是"管道"命令行指令,例如:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'
Run Code Online (Sandbox Code Playgroud)

"管道" |可以被视为MongoDB聚合"管道"中的"管道阶段".

因此我们想要$match从"源"集合中过滤掉第一个操作.这通常是一种很好的做法,因为它可以从其他条件中删除任何不符合要求条件的文档.就像在我们的"命令行管道"示例中发生的那样,我们将"输入"然后"管道"转换grep为"删除"或"过滤器".

路径很重要

你在这里做的下一件事是"加入"通过$lookup.结果是来自"from"集合参数的项的"数组" 与提供的字段匹配,以在"as""字段路径"中作为"数组"输出.

这里选择的命名很重要,因为现在源集合中的"文档"会将"连接"中的所有项目视为现在存在于该给定路径中.为了简化这一点,我使用与"路径"的"连接"相同的"集合"名称.

因此,从第一个"连接"开始,输出将是,"tb2"并且将保存该集合的所有结果.还有要注意与下列顺序一个重要的事情$unwind,然后$match,以MongoDB的实际上是如何处理查询.

某些序列"真的"很重要

因为它"看起来像",也有"三"管道阶段,是$lookup$unwind然后$match.但在"事实"中,MongoDB确实做了其他事情,这{ "explain": true }.aggregate()命令中添加的输出中得到了证明:

    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 
Run Code Online (Sandbox Code Playgroud)

因此,除了应用"序列"的第一点之外,您需要将$match语句放在需要的位置并执行"最佳",这实际上变得"非常重要",具有"连接"的概念.这里要注意的一点是,我们的序列,$lookup然后$unwind,然后$match,真正得到通过MongoDB的处理为刚$lookup阶段,与其他业务"卷起"到每个一个流水线阶段.

这是对"过滤"所得结果的其他方式的重要区别$lookup.由于在这种情况下,对"加入"的实际"查询"条件$match是在集合上执行的,以便在"之前"将结果返回给父级.

如上所示,这与$unwind(其被翻译成unwinding)的结合是MongoDB实际上如何处理"连接"可能导致在源文档中产生内容数组的可能性,这导致它超过16MB BSON限制.这只会在加入的结果非常大的情况下发生,但同样的优点是"过滤器"实际应用的位置,在"之前"返回结果的目标集合上.

正是这种处理最好地"关联"到与SQL JOIN相同的行为.因此,$lookup除了简单地"外部"键值的"本地"之外,它也是从其他条件应用于JOIN的结果获得结果的最有效方式.

另请注意,其他行为更改来自于本质上执行的LEFT JOIN,$lookup无论"target"集合中是否存在匹配文档,都将始终保留"源"文档.相反,$unwind通过"丢弃"来自"源"的任何结果来增加这一点,"源"没有任何与"目标"匹配的附加条件$match.

事实上,由于preserveNullAndEmptyArrays: false包括隐含的内容,它们甚至被预先丢弃,并且会丢弃"本地"和"外国"键在两个集合之间甚至不匹配的任何内容.对于这种特定类型的查询,这是一件好事,因为"join"旨在对这些值进行"相等".

得出结论

如前所述,MongoDB通常将"关系"视为与使用"关系数据库"或RDBMS的方式有很大不同."关系"的一般概念实际上是"嵌入"数据,可以是单个属性,也可以是数组.

你可能实际上需要这样的输出,这也是为什么没有$unwind这里的序列输出$lookup实际上是"数组"的部分原因.然而,$unwind在这种情况下使用实际上是最有效的事情,并且保证"加入"数据实际上不会导致上述BSON限制因"加入"而超过.

如果你真的想要输出数组,那么在这里做的最好的事情就是使用$group管道阶段,并且可能作为多个阶段来"规范化"和"撤消结果"$unwind

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}
Run Code Online (Sandbox Code Playgroud)

事实上你在这个案例中列出了你所需要的所有字段,"tb1"通过它们的属性名称$first来保持"第一次"出现(基本上是由结果"tb2""tb3"展开的重复),然后$push将"细节"从"tb3""数组"变为"数组"代表与...的关系"tb1".

但是给定的聚合管道的一般形式是如何从原始SQL获得结果的精确表示,原始SQL是"连接"的结果的"非规范化"输出.在此之后,您是否想要再次"标准化"结果.