【问题标题】:How to join to two additional collections with conditions如何加入两个有条件的附加集合
【发布时间】:2017-07-07 07:38:40
【问题描述】:
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

实际上我将sql转换为mongo sql如下

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

])

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

任何帮助将不胜感激..

【问题讨论】:

  • 来自 tb1、tb2 和 tb3 的示例 json 文档可能会有所帮助,因为这看起来基本正确。我没有在您的原始查询中看到您从 tb1 中排除 status = 'closed' - 您的 mongo 查询执行此操作。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

您在这里缺少的是$lookup 在其参数中as 指定的输出字段中生成一个“数组”。这是 MongoDB “关系”的一般概念,因为文档之间的“关系”表示为文档本身“内部”的“子属性”,对于许多人来说,要么是单数,要么是“数组”。

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

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

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

顺序很“重要”

聚合管道比 SQL 更“简洁地表达”。实际上,最好将它们视为“一系列步骤”,应用于数据源以整理和转换数据。最好的模拟是“管道”命令行指令,例如:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

“管道”| 可以被视为 MongoDB 聚合“管道”中的“管道阶段”。

因此,我们希望 $match 将“源”集合中的内容过滤为我们的第一个操作。这通常是一种很好的做法,因为它会从进一步的条件中删除任何不符合所需条件的文件。就像我们的“命令行管道”示例中发生的那样,我们将“输入”然后“管道”到grep 以“删除”或“过滤”。

路径很重要

您接下来要做的就是通过$lookup“加入”。结果是来自"from" 集合参数的项目的“数组”,与提供的字段匹配,以在"as"“字段路径”中作为“数组”输出。

此处选择的命名很重要,因为现在源集合中的“文档”认为“连接”中的所有项目现在都存在于该给定路径中。为方便起见,我使用与新“路径”的“连接”相同的“集合”名称。

所以从第一个“加入”开始,输出到"tb2",它将保存该集合的所有结果。对于 MongoDB 实际处理查询的方式,下面的$unwind$match 序列还有一个重要的事情需要注意。

某些序列“真的”很重要

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

    {
        "$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
                }
            }
        }
    }, 

因此,除了应用“序列”的第一点之外,您需要将$match 语句放在需要它们的地方并做“最好的”,这实际上对于“连接”的概念变得“非常重要” .这里要注意的是,我们的$lookup$unwind$match 序列实际上被 MongoDB 处理为只是 $lookup 阶段,而其他操作“汇总”到一个管道阶段每个。

这是与“过滤”$lookup 获得的结果的其他方式的重要区别。因为在这种情况下,来自$match 的“join”的实际“查询”条件是在集合上执行 join“之前”结果返回给父级。

结合上图所示的$unwind(翻译成unwinding)是MongoDB实际处理“连接”可能导致在源文档中产生一个内容数组的可能性,从而导致它超过 16MB BSON 限制。这只会发生在被连接的结果非常大的情况下,但同样的优势在于实际应用“过滤器”的地方,即在返回结果“之前”的目标集合上。

正是这种处理与 SQL JOIN 的相同行为最“相关”。因此,它也是从 $lookup 获取结果的最有效方法,其中除了简单的“外来”键值的“本地”之外,还有其他条件适用于 JOIN。

另请注意,另一个行为变化来自$lookup 执行的本质上的左连接,其中无论“目标”集合中是否存在匹配文档,都将始终保留“源”文档。相反,$unwind 通过在$match 中的附加条件“丢弃”来自“源”的任何结果,这些结果与“目标”没有任何匹配。

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

结束

如前所述,MongoDB 对待“关系”的方式通常与使用“关系数据库”或 RDBMS 的方式大不相同。 “关系”的一般概念实际上是将数据“嵌入”为单个属性或数组。

你可能真的想要这样的输出,这也是为什么没有$unwind 序列,$lookup 的输出实际上是一个“数组”的部分原因。然而,在这种情况下使用$unwind 实际上是最有效的做法,并且保证“加入”数据实际上不会因为“加入”而导致上述 BSON 限制被超过。

如果你真的想要输出数组,那么最好的办法是使用$group 管道阶段,并且可能作为多个阶段来“规范化”和“撤消”$unwind 的结果

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

实际上,在这种情况下,您可以使用 $first 按属性名称列出 "tb1" 所需的所有字段,以仅保留“第一次”出现(基本上由 "tb2""tb3" 的结果重复) unwound ) 然后将$push 中的“细节”从"tb3" 放入一个“数组”中,以表示与"tb1" 的关系。

但是给定的聚合管道的一般形式是如何从原始 SQL 获得结果的精确表示,它是作为“连接”结果的“非规范化”输出。在此之后是否要再次“标准化”结果取决于您。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-03
    • 1970-01-01
    • 2012-10-19
    • 2020-05-12
    • 2018-06-03
    • 2013-03-11
    • 2017-08-10
    • 2021-07-21
    相关资源
    最近更新 更多