几个阵列的交叉点

时间:2016-07-26 11:41:57

标签: mongodb aggregation-framework

我有一些文件有一个数组属性项。 我想得到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 ] }

2 个答案:

答案 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阶段来计算所有文档并将它们推入一个数组供以后使用 - 当然因为我们现在拥有一系列所有可能的文档,所以对于内存消耗来说是一个很大的打击。