我有一个如下集合
{
"state" : "VIC",
"sites" :
[
{
"name" : "VIC01",
"pes" :
[
{
"id" : "1",
"longname" : "rdnvej300exh0443",
"shortname" : "RVE4-E-043",
"location" : "Exhibition"
},
{
"id" : "2",
"longname" : "rdnvej160pee0343",
"shortname" : "RV3W-E-043",
"location" : "Windsor"
},
{
"id" : "3",
"location" : "my home"
}
],
"partners" :
[
{
"id" : "REACH",
"fnns" : ["N54321R","N24686R","N46818R","N10461R"]
},
{
"id" : "NCS_CORE",
"fnns" : [ "N54320R","N71311R","N35797R","N57919R"]
}
]
},
{
"name" : "CLAYTON",
"pes" :
[
{
"id" : "1",
"longname" : "rdnvej1822da0o43",
"shortname" : "RVCZ-E-043",
"location" : "Clayton"
},
{
"id" : "2",
"longname" : "rdnvej1822da0o44",
"shortname" : "RVCZ-E-044",
"location" : "Clayton"
}
],
"partners" :
[
{
"id" : "NCS_CORE",
"fnns" : ["N54331R","N24686R","N46818R","N10461R"]
},
{
"id" : "NCS_CLAYTON_OPS2",
"fnns" : [ "N54321R","N71311R","N35797R","N57919R"]
}
]
}
]
}
我正在尝试检索
{state,sites.name,sites.partners.id}
来自“fnns”列表中“N54321R”的所有文件。
我尝试了下面的查询,但对于上面的内容,它给出了所有的sites.partners.id。
1. db.topology.find( {"sites.partners.fnns" : { "$elemMatch" : { "$eq" : "N54321R" } } }, {"state" : 1 , "sites.name" : 1, "sites.partners.id" : 1 , "sites.partners.fnns" : 1} )
2. db.topology.find( {"sites.partners.fnns" : { $in : ["N54321R"] } }, {"state" : 1 , "sites.name" : 1, "sites.partners.id" : 1 , "sites.partners.fnns" : 1} )
3. db.topology.find( {"sites.partners.fnns" : "N54321R"} , {"state" : 1 , "sites.name" : 1, "sites.partners.id" : 1 , "sites.partners.fnns" : 1} )
正确的输出应该是(只显示sites.partners.id)
{ REACH , NCS_CLAYTON_OPS2 }
但是它给了所有的sites.partners.id,即
{ REACH , NCS_CORE , NCS_CORE , NCS_CLAYTON_OPS2 }
我应该在查询中修改什么来实现结果?
如果需要更改架构,那么请告诉我什么是正确的架构?
感谢。
答案 0 :(得分:4)
基本"预测"通过.find()
方法提供的方法无法按照您要求的方式过滤数组中的内容。外部级别的多个元素的positional $
匹配或指定子文档的特定元素在此处不会处理。
相反,您需要.aggregate()
才能过滤掉内容,因此只需返回这些"合作伙伴"具有匹配值的元素是目前最有效的方式:
db.collection.aggregate([
{ "$match": { "sites.partners.fddn": "N54321R" } },
{ "$project": {
"state": 1,
"sites": {
"$setDifference": [
{ "$map": {
"input": "$sites",
"as": "site",
"in": {
"$let": {
"vars": {
"partners": {
"$setDifference": [
{ "$map": {
"input": "$$site.partners",
"as": "partner",
"in": {
"$cond": [
{ "$setIsSubset": [ ["N54321R"], "$$partner.fnns" ] },
"$$partner",
false
]
}
}},
[false]
]
}
},
"in": {
"$cond": [
{ "$eq": [{ "$size": "$$partners" }, 0] },
false,
{
"name": "$$site.name",
"pes": "$$site.pes",
"partners": "$$partners"
}
]
}
}
}
}},
[false]
]
}
}}
])
将返回过滤结果,如:
{
"_id" : ObjectId("564433c2f7a4adee6c13205b"),
"state" : "VIC",
"sites" : [
{
"name" : "VIC01",
"pes" : [
{
"id" : "1",
"longname" : "rdnvej300exh0443",
"shortname" : "RVE4-E-043",
"location" : "Exhibition"
},
{
"id" : "2",
"longname" : "rdnvej160pee0343",
"shortname" : "RV3W-E-043",
"location" : "Windsor"
},
{
"id" : "3",
"location" : "my home"
}
],
"partners" : [
{
"id" : "REACH",
"fnns" : [
"N54321R",
"N24686R",
"N46818R",
"N10461R"
]
}
]
},
{
"name" : "CLAYTON",
"pes" : [
{
"id" : "1",
"longname" : "rdnvej1822da0o43",
"shortname" : "RVCZ-E-043",
"location" : "Clayton"
},
{
"id" : "2",
"longname" : "rdnvej1822da0o44",
"shortname" : "RVCZ-E-044",
"location" : "Clayton"
}
],
"partners" : [
{
"id" : "NCS_CLAYTON_OPS2",
"fnns" : [
"N54321R",
"N71311R",
"N35797R",
"N57919R"
]
}
]
}
]
}
这里有一些事情,特别是如果你不熟悉聚合框架,那么最好逐步完成它们。
首先是$match
管道阶段,它本质上是一个"查询"就像你发出.find()
的第一个参数一样。在那里使用的路径将选择实际具有匹配元素的文档:
{ "$match": { "sites.partners.fddn": "N54321R" } },
所以sites.partners.fddn
是"路径"正确匹配您要查找的值。在此阶段,"Dot notation"是正确的形式。它匹配一个文档,但是查看位置$
运算符,数组匹配实际上只被计为sites
的元素,而只是第一个匹配发生的元素。这就是为什么这个"投影"对你的目的没有用,但是你仍然应该$match
缩小你真正想要的文件。
然后有$project
,它是标准投影可以完成的扩展形式,实际上允许使用.find()
无法完成的完整文档操作。
在这个中你有复合数组,这些是你需要检查和过滤的。这方面的工具分别是$map
和$setDifference
。
基本上,$map
查看数组的每个元素,并允许针对每个元素处理条件。在这种情况下,您需要的操作是$cond
,这是一个三元运算符。它根据条件的第一个参数返回一个值,它是true
的第二个参数或false
的第三个参数。
如果你先看看最里面的部分:
{ "$map": {
"input": "$$site.partners",
"as": "partner",
"in": {
"$cond": [
{ "$setIsSubset": [ ["N54321R"], "$$partner.fnns" ] },
"$$partner",
false
]
}
}},
这是关注内部"合作伙伴"的每个要素。数组并对其进行另一次比较" ffns"阵列。加快一点是$setIsSubset
,它比较你的值(指定为数组/集合本身),看它是否实际上是一个"子集" (包含的成员)正在测试的阵列/集合。这是另一个逻辑true/false
结果,也会输入$cond
,要么返回元素,要么false
不符合条件。
各个地方显示的$setDifference
始终执行相同的工作,因为$map
只返回每个元素的false
结果的所需值,此比较将全部删除false
元素,只留下具有匹配值的内容。
最后有$let
允许声明变量以避免重复声明逻辑。虽然它不适用于此示例中的数据,但可能会导致"合作伙伴"实际上可能是一个空数组。通常在结果中不需要这样做。
因此,在"网站"的$map
处理中数组,我们计算出"合作伙伴"的每个过滤结果。首先,然后测试结果,看看它是否确实有一些长度。如果没有,则返回类似的false
,否则返回元素中的内容""过滤版本的"合作伙伴"代替每个成员的原始数组。然后应用$false
的相同过滤。
可能需要一点点,但值得学习和理解。也可以使用$unwind
的阶段进行处理,但应该避免,因为这会对性能产生重大影响。但是,此处显示的聚合管道与任何标准查询的性能特征基本相同,只需很少的额外开销。
答案 1 :(得分:0)
您的收藏集只有一个文档。它有一个带有嵌套对象数组的复杂子结构,但db.topology.find()
总是会在此集合上返回0或1个文档。该文档将包含所有所有嵌套数组的元素。
将$elemMatch
想象为找到我所有完整的文档,其中任何数组元素包含指定的值。这与你的内容非常不同尝试做的是找到包含指定值的文档中的所有数组元素,并排除那些不包含的数组元素。
为了能够独立搜索合作伙伴,您可以为网站, pes 和合作伙伴创建单独的收藏集并让 pes 和合作伙伴包含引用父网站的密钥。