如何在mongodb中进行条件嵌套查找搜索

时间:2019-10-11 21:25:43

标签: mongodb

我在mongodb 4.2中有2个收藏集:

  • 文章-[Id,ArticletypeId,BestResponseId,Topic,PredecessorId]

      { Id: 1, ArticleTypeId:1, BestResponseId:2, Topic:"XYZ" },
      { Id: 2, ArticleTypeId:2, PredecessorId:1 },
      { Id: 3, ArticleTypeId:2, PredecessorId:1 },
      { Id: 4, ArticleTypeId:2, BestResponseId:5, Topic:"ABC" },  
      { Id: 5, ArticleTypeId:1, PredecessorId:4 },
      { Id: 6, ArticleTypeId:2, PredecessorId:4 }
    
  • 结果-[Id,ArticleId,ResultTypeId]

    { Id: 1, ArticleId:1, ResultTypeId:2 },
    { Id: 2, ArticleId:2, ResultTypeId:2 },
    { Id: 3, ArticleId:2, ResultTypeId:2 },
    { Id: 4, ArticleId:3, ResultTypeId:2 },
    { Id: 5, ArticleId:2, ResultTypeId:2 },
    { Id: 6, ArticleId:4, ResultTypeId:2 },
    { Id: 7, ArticleId:5, ResultTypeId:2 },
    { Id: 8, ArticleId:6, ResultTypeId:2 },
    { Id: 9, ArticleId:6, ResultTypeId:2 }
    
  • 在文章集合中,BestResponseId是对给定文章的最佳响应的ArticleId,即,对于具有主题“ XYZ”的ArticleId = 1,最佳响应是ArticleId = 2,依此类推。

  • PredecessorId指示响应是针对哪篇文章的。

  • 在结果集合中,ArticleId是引用article-Id的外键

  • 我们需要找到主题列表,其中AnyResponse中的Count(ResultTypeId = 2)大于BestResponse ,因此在以下示例中:

  • 对于1-5的结果,ArticleId(2)的Count(ResultTypeId = 2)为2,但对于针对同一文章的其他响应,Count(ResultTypeId = 2)为1,因此最佳响应得到了最好的结果,我们将不在输出中考虑。

  • 但是这里是6-9的其他结果:ArticleId(5)的Count(ResultTypeId = 2)为1,其中ArticleId(5)的Count(ResultTypeId = 2)为2, 因此预期的输出将是

  • 主题

  • “ ABC”

  • 因此,基本上,您在article和article [id和 PredecessorId,[自行加入],获取PredecessorId的列表以及 其中的一个是BestResponseId,因此是第一级查找 应该给出如下输出:

    PredecessorId|ArticleId|IsBestResponse
    1           |2       |  true
    1           |3       |  false
    4           |5       |  true
    4           |6       |  false
    
  • 现在,一旦将其与result(ArticleId)一起加入,并计算 ResultTypeId = 2按文章ID分组。所以第二级之后 查找,输出将是:

    ArticleId|PredecessorId|IsBestResponse|ResultType2_Count
        2     |   1         |    true      |  3
        3     |   1         |    false     |  1
        5     |   4         |    true      |  1
        6     |   4         |    false     |  2
    
  • 现在,我们需要输出的前身的主题名称 文章为IsBestResponse = false但为ResultType2_Count的文章 大于为其的文章的ResultType2_Count IsBestResponse = true属于同一前任。

  • 因此,在ArticleId 5和6之间,此条件可以满足。 他们的前身[“ ABC”]对应的主题是预期的输出。
  • 如果2和满足相同条件,我们将打印“ XYZ” 也是。但这不是。

我是mongodb和查找的新手,这是我到目前为止所做的:

db.article.aggregate([
{
$lookup:{
from:"article",
localField:"ArticleId",
foreignField:"PredecessorId",
as:"articles"
}
},
{$unwind:"$articles"},
{$lookup:{
from:"result",
localField:"answers.Id",
foreignField:"ArticleId",
as:"articles"
}},
{$unwind:"$articles"}
])

我确定我需要在第二层嵌套查询中执行$ sum或$ count。 有什么办法可以在同一查询中完成它? 预先谢谢你!

1 个答案:

答案 0 :(得分:2)

因此,您所寻找的实际上是以下内容:

db.article.aggregate([
  { "$match": { "Topic": { "$exists": true } } },
  { "$lookup": {
    "from": "article",
    "let": { "id": "$Id", "bestResponse": "$BestResponseId" },
    "pipeline": [
      { "$match": { 
        "$expr": { "$eq": [ "$$id", "$PredecessorId" ] }
      }},
      { "$lookup": {
        "from": "result",
        "let": { "articleId": "$Id" },
        "pipeline": [
          { "$match": {
            "ResultTypeId": 2,
            "$expr": { "$eq": [ "$$articleId", "$ArticleId" ] }
          }},
          { "$count": "count" }
        ],
        "as": "results"
      }},
      { "$addFields": {
        "results": "$$REMOVE",
        "count": { "$sum": "$results.count" },
        "isBestResponse": { "$eq": ["$$bestResponse", "$Id"] }
      }}
    ],
    "as": "responses"
  }},
  { "$match": {
     "$expr": {
       "$gt": [
         { "$max": "$responses.count" },
         { "$arrayElemAt": [
           "$responses.count",
           { "$indexOfArray": [ "$responses.Id", "$BestResponseId" ] }
         ]}
       ]
     }
  }}
])

这将提供(与您要解释的关系输出相比,更多的MongoDB类似输出):

{
        "_id" : ObjectId("5da1206f22b8db5a00668cc4"),
        "Id" : 4,
        "ArticleTypeId" : 2,
        "BestResponseId" : 5,
        "Topic" : "ABC",
        "responses" : [
                {
                        "_id" : ObjectId("5da1206f22b8db5a00668cc5"),
                        "Id" : 5,
                        "ArticleTypeId" : 1,
                        "PredecessorId" : 4,
                        "count" : 1,
                        "isBestResponse" : true
                },
                {
                        "_id" : ObjectId("5da1206f22b8db5a00668cc6"),
                        "Id" : 6,
                        "ArticleTypeId" : 2,
                        "PredecessorId" : 4,
                        "count" : 2,
                        "isBestResponse" : false
                }
        ]
}

现在,我将逐步解释它的原因。

首先,您希望在流水线开始时的$match阶段仅排除那些有效的Topic结果以外的任何内容。这使用了一个简单的$exists以便从该字段中检索那些结果,然后满足第一个“ join”的条件。

实际的$lookup将使用modern form with a pipeline expression。这是由于以下两个主要原因:

  • 我们实际上希望一个“内部” $lookup表达式从另一个集合中获取结果。

  • 我们要对返回结果 进行操作“作为数组” ,这是$lookup的输出始终。这比操作之后返回的“数组”更有效。

此语法中要注意的一件事是let表达式:

    "let": { "id": "$Id", "bestResponse": "$BestResponseId" },

这里最常见的用例是提供 parent 文档中的值,该值可在初始{{3}内的$expr逻辑中使用}表示“加入”条件,即哪个字段值与 local foreign 匹配。但是在这种情况下,我们实际上还有另一个有效用法,尤其是声明的bestResponse值。

请注意,一旦我们有“加入”(即“自我加入” 部分)以获取相关的子项,那么接下来我们想要的就是嵌套另一个$matchpipeline表达式中。在这种情况下,我们希望在 自己的pipeline表达式中的 初始$lookup阶段对ResultTypeId: 2使用附加约束,即问题的一部分。基本上,这就是您可以在“ join”上包括多个条件的方法。

由于我们实际上对result集合中的嵌套详细信息不感兴趣,并且实际上不需要在其他“ children” 数组中的results数组,因此为了 减少 ,我们在此子管道中使用$match管道阶段。

现在,这还不完全是您想要的,因此,在其pipeline表达式中的初始$count操作中,您可以添加$lookup阶段来操纵本质上是 array的内容放在results属性中(尽管只有一个文档具有一个属性),通过$addFields运算符将其变成每个子对象中具有唯一值的单个属性。您可以这样做:

"count": { "$arrayElemAt": [ "$results.count", 0 ] }

这将是相同的结果,但是值得注意的是,它的表达更长,而不仅仅是"$sum": "$results.count"

您想要的另一件事(尽管对于其余逻辑不是真正必要的)是识别哪个“子” 实际上与BestResponseId值匹配。这实际上是我们使用先前声明的bestResponse变量的方式。由于这是 parent 中的值,因此可以针对 中的每个 child 对其进行处理,并且只需返回truefalse,其中当前的 child Id字段实际上与 parent 中的值匹配。

一旦退出$sum管道阶段,剩下要做的就是确定“联接”之后的 哪一个结果文档实际上满足拥有带有以下条件的文章的条件:比标为“ BestResponse” 的结果更高。这是通过另一个$lookup流水线阶段完成的,该阶段再次使用$match运算符。

简而言之,$expr用于获取每个 child 条目中返回的count值的 maximum 作为{{1 $max中的}}数组。将其与$lookup运算符获得的值与responses数组中与父Id匹配的responses数组中BestResponseId字段的值进行匹配(或者,其中isBestResponsetrue。但这就是为什么我指出并不需要)。有了匹配的“索引值”,您就可以通过$indexOfArray从该数组中提取count属性的奇异值并进行比较。如果它实际上是一个大于的数字,则该文档符合返回结果的条件。

当然,如果您想使用其他$arrayElemAt甚至$project$addFields“反规范化” ,则只需返回带有原始字段的文档即可您再次想要一个看起来与SQL“ join”结果相同的结果。但是基本逻辑实际上只需要三个阶段(以及$unwind 以内$lookup内)作为实现的基本部分。