如何将MongoDB聚合用于通用集合操作(​​并集,交集,差异)

时间:2013-06-24 05:52:47

标签: mongodb set aggregation-framework set-intersection

我遇到了一些特殊目的的集合操作实现,但对于一般情况却没有。执行集合运算的一般情况是什么(特别是交集,并集,对称差异)。这更容易理解在$ where或map reduce中使用javascript,但我想知道如何在聚合中执行此操作以获得本机性能。

说明这个问题的更好方法是举个例子。假设我有2个数组/集的记录:

db.colors.insert({
    _id: 1,
    left : ['red', 'green'],
    right : ['green', 'blue']
});

我想找到'左'和'右'数组的并集,交集和差异。更好的是,我想找到:

联盟 - > ['red','green','blue']

union

交叉点 - > [ '绿色']

enter image description here

对称差异 - > ['red','blue']

enter image description here

2 个答案:

答案 0 :(得分:4)

仅限版本2.6+:

从MongoDB的2.6版本开始,这变得容易得多。您现在可以执行以下操作来解决此问题:

<强>联盟

db.colors.aggregate([
    {'$project': {  
                    union:{$setUnion:["$left","$right"]}
                 }
    }
]);

<强>交叉口

db.colors.aggregate([
    {'$project': {  
                  int:{$setIntersection:["$left","$right"]}
                 }
    }
]);

相对补充

db.colors.aggregate([
    {'$project': {  
                    diff:{$setDifference:["$left","$right"]}
                 }
    }
]);

对称差异

db.colors.aggregate([
    {'$project': {  
                    diff:{$setUnion:[{$setDifference:["$left","$right"]}, {$setDifference:["$right","$left"]}]}
                 }
    }
]);

注意:有一个ticket请求对称差异作为核心功能添加,而不是必须进行两个集合差异的联合。

答案 1 :(得分:3)

使用聚合这三个中最容易的是交集**。一般情况可以使用如下聚合来完成:

<强>路口:

db.colors.aggregate([
    {'$unwind' : "$left"},
    {'$unwind' : "$right"},
    {'$project': {  
                    value:"$left", 
                    same:{$cond:[{$eq:["$left","$right"]}, 1, 0]}
                 }
    },
    {'$group'  : { 
                    _id: {id:'$_id', val:'$value'}, 
                    doesMatch:{$max:"$same"}
                 }
    },
    {'$match'   :{doesMatch:1}},
]);

另外两个变得有点棘手。据我所知,没有办法将同一文件中的两个单独的字段组合在一起。在$ project管道阶段有$ add,$ combine或$ addToSet会很不错,但这不存在。所以我们能做的最好的事情就是说某些东西是否相交。我们可以使用以下内容启动两个聚合:

db.colors.aggregate([
    {'$unwind' : "$left"},
    {'$unwind' : "$right"},
    {'$project': {  
                    left:"$left",
                    right:'$right',
                    same:{$cond:[{$eq:["$left","$right"]}, 1, 0]}
                 }
    },
    {'$group'  : {
                    _id:{id:'$_id', left:'$left'},
                    right:{'$addToSet':'$right'},
                    sum: {'$sum':'$same'},
                 }
    },
    {'$project': {  
                    left:{val:"$_id.left",inter:"$sum"},
                    right:'$right',
                 }
    },
    {'$unwind' : "$right"},
    {'$project': {  
                    left:"$left",
                    right:'$right',
                    same:{$cond:[{$eq:["$left.val","$right"]}, 1, 0]}
                 }
    },
    {'$group'  : {
                    _id:{id:'$_id.id', right:'$right'},
                    left:{'$addToSet':'$left'},
                    sum: {'$sum':'$same'},
                 }
    },
    {'$project': {  
                    right:{val:"$_id.right",inter:"$sum"},
                    left:'$left',
                 }
    },
    {'$unwind' : "$left"},
    {'$group'  : {
                    _id:'$_id.id',
                    left:{'$addToSet':'$left'},
                    right: {'$addToSet':'$right'},
                 }
    },
]);

问题中提供的样本聚合将得到如下结果:

{
        "_id" : 1,
        "left" : [
                {
                        "val" : "green",
                        "inter" : 1
                },
                {
                        "val" : "red",
                        "inter" : 0
                }
        ],
        "right" : [
                {
                        "val" : "blue",
                        "inter" : 0
                },
                {
                        "val" : "green",
                        "inter" : 1
                }
        ]
}

从这里我们可以通过在聚合中添加以下内容来获得交集:

{'$project': {  
                    left:"$left"
                 }
    },
    {'$unwind' : "$left"},
    {'$match'  : {'left.inter': 1}},
    {'$group'  : {
                    _id:'$_id',
                    left:{'$addToSet':'$left'},
                 }
    },

我们可以通过在基础聚合的末尾添加以下内容来找到差异以及相对补充:

enter image description here

{'$unwind' : "$left"},
    {'$match'  : {'left.inter': 0}},
    {'$unwind' : "$right"},
    {'$match'  : {'right.inter': 0}},
    {'$group'  : {
                    _id:'$_id',
                    left:{'$addToSet':'$left'},
                    right:{'$addToSet':'$right'},
                 }
    },

不幸的是,似乎没有一种好方法可以将来自不同领域的不同项目组合在一起。为了获得联盟,最好从客户那里做到这一点。或者,如果您想要过滤,请分别在每个集合上进行过滤。