Как использовать агрегацию MongoDB для операций общего назначения (объединение, пересечение, разность)
Я столкнулся с некоторыми целенаправленными реализациями заданных операций, но ничего для общего случая. Каков общий случай выполнения операций установки (в частности, пересечение, объединение, симметричная разность). Это проще понять с помощью javascript в $where или map reduce, но я хочу знать, как это сделать в агрегации, чтобы получить собственную производительность.
Лучшим способом проиллюстрировать этот вопрос является пример. Скажем, у меня есть запись с двумя массивами/наборами:
db.colors.insert({
_id: 1,
left : ['red', 'green'],
right : ['green', 'blue']
});
Я хочу найти объединение, пересечение и различие в "левых" и "правильных" массивах. Еще лучше, наглядно я хочу найти:
Союз → ['красный', 'зеленый', 'синий']
![union]()
Пересечение → ['green']
![enter image description here]()
Симметричная разница → ['red', 'blue']
![enter image description here]()
Ответы
Ответ 1
Версия 2.6+ Только:
Начиная с версии 2.6 MongoDB, это стало намного проще. Вы можете сделать следующее для решения этой проблемы:
Union
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"]}]}
}
}
]);
Примечание: существует билет, требующий симметричной разницы, добавляемый как основная функция, а не необходимость объединения двух заданных различий.
Ответ 2
Простейшим из этих трех, использующих агрегацию, является пересечение **. Общий случай для этого можно сделать, используя агрегацию следующим образом:
Пересечения:
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}},
]);
Остальные два становятся немного более сложными. Насколько я знаю, нет единого способа объединить два отдельных поля в одном документе. Было бы неплохо иметь $add, $comb или $addToSet в фазе проекта $project, но этого не существует. Поэтому лучшее, что мы можем сделать, это сказать, что что-то пересекло или нет. Мы можем начать обе скопления со следующим:
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'},
}
},
К сожалению, похоже, что нет хорошего способа объединить разнородные элементы из разных полей. Чтобы получить союз, лучше всего сделать это от клиента. Или, если вы хотите фильтровать, делайте это по каждому набору отдельно.