我有一些文件有一个数组属性项。 我想得到n个文件之间的拦截。
db.things.insert({name:"A", items:[1,2,3,4,5]})
db.things.insert({name:"B", items:[2,4,6,8]})
db.things.insert({name:"C", items:[1,2]})
db.things.insert({name:"D", items:[5,6]})
db.things.insert({name:"E", items:[9,10]})
db.things.insert({name:"F", items:[1,5]})
数据:
{ "_id" : ObjectId("57974a0d356baff265710a1c"), "name" : "A", "items" : [ 1, 2, 3, 4, 5 ] },
{ "_id" : ObjectId("57974a0d356baff265710a1d"), "name" : "B", "items" : [ 2, 4, 6, 8 ] },
{ "_id" : ObjectId("57974a0d356baff265710a1e"), "name" : "C", "items" : [ 1, 2 ] },
{ "_id" : ObjectId("57974a0d356baff265710a1f"), "name" : "D", "items" : [ 5, 6 ] },
{ "_id" : ObjectId("57974a0d356baff265710a20"), "name" : "E", "items" : [ 9, 10 ] },
{ "_id" : ObjectId("57974a1a356baff265710a21"), "name" : "F", "items" : [ 1, 5 ] }
例如: things.mane.A拦截things.mane.C拦截things.mane.F:
[1,2,3,4,5]拦截[1,2]拦截[1,5]
必须:[1]
我认为使用$ setIntersection是可行的,但我无法找到方法。
我可以用两个文件来做,但如何用更多的文件来做呢?
db.things.aggregate({$match:{"name":{$in:["A", "F"]}}},
{$group:{_id:null, "setA":{$first:"$items"}, "setF":{$last:"$items"} } },
{
"$project": {
"set1": 1,
"set2": 1,
"commonToBoth": { "$setIntersection": [ "$setA", "$setF" ] },
"_id": 0
}
}
)
{ "commonToBoth" : [ 5, 1 ] }
答案 0 :(得分:1)
如果您使用的是mongo 3.2,则可以使用arrayElemAt
来准确$setIntersection
的所有参数:
db.things.aggregate([{
$match: {
"name": {
$in: ["A", "B", "C"]
}
}
}, {
$group: {
_id: 0,
elements: {
$push: "$items"
}
}
}, {
$project: {
intersect: {
$setIntersection: [{
"$arrayElemAt": ["$elements", 0]
}, {
"$arrayElemAt": ["$elements", 1]
}, {
"$arrayElemAt": ["$elements", 2]
}]
},
}
}]);
您必须使用索引动态添加需要数量的JsonObject,例如:
{
"$arrayElemAt": ["$elements", <index>]
}
它应与["A", "B", "C"]
如果您要处理重复项(多次出现name
次),请按name
,$unwind
重新组合所有项目两次,然后$addToSet
合并所有数组对于执行上一个聚合之前的特定$name
:
db.things.aggregate([{
$match: {
"name": {
$in: ["A", "B", "C"]
}
}
}, {
$group: {
_id: "$name",
"items": {
"$push": "$items"
}
}
}, {
"$unwind": "$items"
}, {
"$unwind": "$items"
}, {
$group: {
_id: "$_id",
items: {
$addToSet: "$items"
}
}
}, {
$group: {
_id: 0,
elements: {
$push: "$items"
}
}
}, {
$project: {
intersect: {
$setIntersection: [{
"$arrayElemAt": ["$elements", 0]
}, {
"$arrayElemAt": ["$elements", 1]
}, {
"$arrayElemAt": ["$elements", 2]
}]
},
}
}]);
这不是一个干净的解决方案,但它有效
答案 1 :(得分:1)
不特定于输入项目数量的解决方案可能如下所示:
db.things.aggregate(
{
$match: {
"name": {
$in: ["A", "F"]
}
}
},
{
$group: {
_id: "$items",
count: {
$sum: 1
}
}
},
{
$group: {
_id: null,
totalCount: {
$sum: "$count"
},
items: {
$push: "$_id"
}
}
},
{
$unwind: {
path: "$items"
}
},
{
$unwind: {
path: "$items"
}
},
{
$group: {
_id: "$items",
totalCount: {
$first: "$totalCount"
},
count: {
$sum: 1
}
}
},
{
$project: {
_id: 1,
presentInAllDocs: {
$eq: ["$totalCount", "$count"]
}
}
},
{
$match: {
presentInAllDocs: true
}
},
{
$group: {
_id: null,
items: {
$push: "$_id"
}
}
}
)
将输出此
{
"_id" : null,
"items" : [
5,
1
]
}
当然,您可以添加最后一个$project
阶段,以将结果转换为所需的形状。
解释
这背后的基本思想是,当我们计算文件数量并计算每个项目的出现次数时,每个文件中出现计数等于总文件数的项目,因此在交叉点结果。
这个想法有一个重要的假设:你的items
数组中没有重复数据(即它们是集合)。如果这个假设是错误的,那么你必须在管道的开头插入一个额外的阶段来将数组转换成集合
也可以用不同的,可能更短的方式构建这个管道,但我试图尽可能地保持资源使用率,因此可能不必要(从功能的角度来看)添加阶段。例如,items
数组的第二阶段组作为我的假设是,与文档相比,不同的值/数组要少得多,因此管道的其余部分必须使用初始文档计数的一小部分。但是,从功能的角度来看,我们只需要文档的总数,因此我们可以跳过那个阶段,只需创建一个$group
阶段来计算所有文档并将它们推入一个数组供以后使用 - 当然因为我们现在拥有一系列所有可能的文档,所以对于内存消耗来说是一个很大的打击。