我在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属于同一前任。
我是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。 有什么办法可以在同一查询中完成它? 预先谢谢你!
答案 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。这是由于以下两个主要原因:
此语法中要注意的一件事是let
表达式:
"let": { "id": "$Id", "bestResponse": "$BestResponseId" },
这里最常见的用例是提供 parent 文档中的值,该值可在初始{{3}内的$expr
逻辑中使用}表示“加入”条件,即哪个字段值与 local 和 foreign 匹配。但是在这种情况下,我们实际上还有另一个有效用法,尤其是声明的bestResponse
值。
请注意,一旦我们有“加入”(即“自我加入” 部分)以获取相关的子项,那么接下来我们想要的就是嵌套另一个$match
在pipeline
表达式中。在这种情况下,我们希望在 自己的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 对其进行处理,并且只需返回true
或false
,其中当前的 child Id
字段实际上与 parent 中的值匹配。
一旦退出$sum
管道阶段,剩下要做的就是确定“联接”之后的 哪一个结果文档实际上满足拥有带有以下条件的文章的条件:比标为“ BestResponse” 的结果更高。这是通过另一个$lookup
流水线阶段完成的,该阶段再次使用$match
运算符。
简而言之,$expr
用于获取每个 child 条目中返回的count
值的 maximum 作为{{1 $max
中的}}数组。将其与$lookup
运算符获得的值与responses
数组中与父Id
匹配的responses
数组中BestResponseId
字段的值进行匹配(或者,其中isBestResponse
是true
。但这就是为什么我指出并不需要)。有了匹配的“索引值”,您就可以通过$indexOfArray
从该数组中提取count
属性的奇异值并进行比较。如果它实际上是一个大于的数字,则该文档符合返回结果的条件。
当然,如果您想使用其他$arrayElemAt
甚至$project
或$addFields
来“反规范化” ,则只需返回带有原始字段的文档即可您再次想要一个看起来与SQL“ join”结果相同的结果。但是基本逻辑实际上只需要三个阶段(以及$unwind
以内在$lookup
内)作为实现的基本部分。