The easiest of these three using aggregation is the intersection**. The general case for that can be done using aggregation like so:
Intersection:
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}},
]);
The other two become a bit more tricky. To my knowledge there isn't a way of combining two separate fields in the same document together. It would be nice to have an $add, $combine, or $addToSet in the $project pipeline phase, but this doesn't exist. So the best we can do is say if something has intersected or not. We can start both aggregations with the following:
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'},
}
},
]);
This aggregation on the sample provided in the question will give a result like this:
{
"_id" : 1,
"left" : [
{
"val" : "green",
"inter" : 1
},
{
"val" : "red",
"inter" : 0
}
],
"right" : [
{
"val" : "blue",
"inter" : 0
},
{
"val" : "green",
"inter" : 1
}
]
}
From here we can get the intersection by adding the following to the aggregation:
{'$project': {
left:"$left"
}
},
{'$unwind' : "$left"},
{'$match' : {'left.inter': 1}},
{'$group' : {
_id:'$_id',
left:{'$addToSet':'$left'},
}
},
We can find the difference as well as the relative complement by adding the following to the end of the base aggregation:
{'$unwind' : "$left"},
{'$match' : {'left.inter': 0}},
{'$unwind' : "$right"},
{'$match' : {'right.inter': 0}},
{'$group' : {
_id:'$_id',
left:{'$addToSet':'$left'},
right:{'$addToSet':'$right'},
}
},
Unfortunately there does not appear to be a good way to combine dissimilar items from different fields together. In order to get the union, it seems best to do that from the client. Or if you want filtering, do it on each set individually.