在MongoDB中,如何返回以下所有文档:
例如,鉴于以下集合,我想匹配以下所有文件:
channelId
dayOfWeek
等于星期一在满足1& 1的所有文件之间2,它们也应该集体匹配产品ID [1,2]。也就是说,如果我的channelId
与dayOfWeek
匹配,则它还必须包含productId
为1的单个文档和productId
为2的其他文档。
{
channelId: "ID-A",
dayOfWeek: "MONDAY",
productId: "1"
},
{
channelId: "ID-A",
dayOfWeek: "MONDAY",
productId: "2"
},
{
channelId: "ID-B",
dayOfWeek: "MONDAY",
productId: "1"
},
{
channelId: "ID-B",
dayOfWeek: "MONDAY",
productId: "3"
},
{
channelId: "ID-C",
dayOfWeek: "MONDAY",
productId: "1"
},
{
channelId: "ID-C",
dayOfWeek: "TUESDAY",
productId: "2"
}
在这种情况下,期望的回报是:
{
channelId: "ID-A",
dayOfWeek: "MONDAY",
productId: "1"
},
{
channelId: "ID-A",
dayOfWeek: "MONDAY",
productId: "2"
}
productId
等于2. dayOfWeek
。答案 0 :(得分:1)
基本上,在对dayOfWeek
的常见条件进行过滤后,需要通过公共密钥$group
将所有内容放在一起,然后查看分组是否确实“配对”并实际包含两个结果想要“相交”:
db.collection.aggregate([
{ "$match": { "dayOfWeek": "MONDAY" } },
{ "$group": {
"_id": "$channelId",
"docs": { "$push": "$$ROOT" },
"count": { "$sum": 1 }
}},
{ "$match": {
"count": { "$gt": 1 },
"docs": {
"$all": [
{ "$elemMatch": { "productId": "1" } },
{ "$elemMatch": { "productId": "2" } }
]
}
}}
])
在$all
内部$elemMatch
条件“分组”后,确保在阵列内的“分组”文档中满足“两个”条件。另请注意,如果您实际上意味着“只有两个”,那么您可以查找"count": 2
,而不仅仅是"count": { "$gt": 1 }
,这里的示例意味着“分组”至少与“某事”配对,如果不完全一样“二”。
这基本上返回一个结果,每个分组的匹配文档为:
{
"_id" : "ID-A",
"docs" : [
{
"_id" : ObjectId("5b22f455fe0315289f716483"),
"channelId" : "ID-A",
"dayOfWeek" : "MONDAY",
"productId" : "1"
},
{
"_id" : ObjectId("5b22f455fe0315289f716484"),
"channelId" : "ID-A",
"dayOfWeek" : "MONDAY",
"productId" : "2"
}
],
"count" : 2
}
如果您需要“仅文档”作为结果,那么如果您的支持MongoDB版本超过3.4,则可以在$unwind
之后$replaceRoot
进一步使用该文档:
db.collection.aggregate([
{ "$match": { "dayOfWeek": "MONDAY" } },
{ "$group": {
"_id": "$channelId",
"docs": { "$push": "$$ROOT" },
"count": { "$sum": 1 }
}},
{ "$match": {
"count": { "$gt": 1 },
"docs": {
"$all": [
{ "$elemMatch": { "productId": "1" } },
{ "$elemMatch": { "productId": "2" } }
]
}
}},
{ "$unwind": "$docs" },
{ "$replaceRoot": { "newRoot": "$docs" } }
])
或使用$project
并明确命名不包含的所有字段:
db.collection.aggregate([
{ "$match": { "dayOfWeek": "MONDAY" } },
{ "$group": {
"_id": "$channelId",
"docs": {
"$push": {
"_id": "$_id",
"channelId": "$channelId",
"dayOfWeek": "$dayOfWeek",
"productId": "$productId
}
},
"count": { "$sum": 1 }
}},
{ "$match": {
"count": { "$gt": 1 },
"docs": {
"$all": [
{ "$elemMatch": { "productId": "1" } },
{ "$elemMatch": { "productId": "2" } }
]
}
}},
{ "$unwind": "$docs" },
{ "$project": {
"_id": "$docs._id",
"channelId": "$docs.channelId",
"dayOfWeek": "$docs.dayOfWeek",
"productId": "$docs.productId"
}}
])
实际上,在最后一种形式中,自从聚合框架与2.2版一起发布以来,该语句基本上与MongoDB的每个版本兼容。
或者,只要您拥有带$setIsSubset
的MongoDB 3.6或更高版本,您就可以“使用”$expr
运算符:
db.collection.aggregate([
{ "$match": { "dayOfWeek": "MONDAY" } },
{ "$group": {
"_id": "$channelId",
"docs": { "$push": "$$ROOT" },
"count": { "$sum": 1 }
}},
{ "$match": {
"count": { "$gt": 0 },
"$expr": {
"$setIsSubset": [ [ "1", "2" ], "$docs.productId" ]
}
}}
])
你甚至可以使用$redact
或使用$project
后跟另一个$match
来改变它,但实际上并不是说你可能认为“设置操作符”这些实际上不是最适合您在这里寻找的特定结果。
请注意,任何类型的“交集”或“子集”基本上都依赖于能够将文档相互比较。这实质上意味着将“分组”的东西放入一个数组中进行这样的比较。如果实际结果大小导致这样的“分组”到exceed the BSON limit,那么它们实际上不能使用这样的方法,除了通过游标将匹配的文档加载到初始查询过滤器并检查之外别无选择。 / p>
因此,对于“完整性”,您可以考虑在这种情况下,$lookup
可以使用“自引用连接”,而不是使用$push
来累积匹配的文档:
db.collection.aggregate([
{ "$match": { "dayOfWeek": "MONDAY" } },
{ "$group": {
"_id": "$channelId",
"count": { "$sum": 1 }
}},
{ "$match": { "count": { "$gt": 1 } } }, // keep only "multiple" groups
{ "$lookup": {
"from": "collection",
"localField": "_id",
"foreignField": "channelId",
"as": "docs"
}},
{ "$unwind": "$docs" },
// ** See note below about the $match **
//{ "$match": { "docs.productId": { "$in": [ "1", "2" ] } } },
])
这里的优势是"docs"
的“数组”实际上从未根据$lookup
+ $unwind
Coalescence的特殊处理构建,它基本上将unwinding
动作“卷起”在"MONDAY"
内{3}}本身。通过这种方式,您可以通过$lookup
操作获得相同的文档,但已经以不会破坏$push
的方式“分离”到自己的文档中。
然而,在这种形式下,实际上无法比较“集合”,因为您需要“数组”以查看“分组”项目是否在“集合”内。因此,实际上正在避免“分组”行动以避免限制违规。尽管如此,这通常比简单地将匹配文档的光标迭代到"channelId"
更好,因为您已经通过"1"
指示了“分组”结果。
可以在那里进行的唯一其他比较是使用16MB BSON limit使用$match
。这将再次“卷起”到实际的$in
操作中,以有效地仅返回那些也匹配该条件的文档。然而,结果基本上是“否定”,因为使用$lookup
初始查询可以实现相同的结果,当然也就是说 “only” 那些包含"2"
或SP
的文档,而不包含任何其他关于这些值的文档,而不是“子集的一部分”。