2013-06-24 2 views
10

Я столкнулся с некоторыми целенаправленными реализациями заданных операций, но ничего общего не имеет. Каков общий случай выполнения операций установки (в частности, пересечение, объединение, симметричная разность). Это проще понять, используя javascript в $ where или map reduce, но я хочу знать, как это сделать в агрегации, чтобы получить собственную производительность.Как использовать агрегацию MongoDB для операций общего назначения (объединение, пересечение, разность)

Лучшим способом проиллюстрировать этот вопрос является пример. Скажем, у меня есть запись с 2 массивов/наборов:

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

Я хочу найти объединение, пересечение и разность «левых» и массивов «правильных». Еще лучше, изобразительно я хочу найти:

Союз -> [ 'красный', 'зеленый', 'синий']

union

Пересечения -> [ 'зеленый ']

enter image description here

Симметричный разница -> [' красный», 'синий']

enter image description here

+0

Этот вопрос был вдохновлен этим [ответом] (http://stackoverflow.com/a/17266323/311525). Я нашел несколько конкретных случаев, но не общие случаи, которые я искал. – Scott

+0

Возможный дубликат [Использовать агрегацию MongoDB для поиска множества пересечений двух наборов в одном документе] (http://stackoverflow.com/questions/17264017/use-mongodb-aggregation-to-find-set-intersection-of-two -sets-in-the-same-doc) – WiredPrairie

+0

@WiredPrairie, учитывая, что тот, с которым вы связаны, является моим, я могу заверить вас, что они не дублируют вопросы. Тот, с которым вы связаны, является очень конкретным случаем, когда найдено подмножество. Это более общий более общий вопрос о пересечении, союзах и различиях. – Scott

ответ

4

Версия 2.Только 6+:

Начиная с версии 2.6 MongoDB это стало намного проще. Теперь вы можете сделать следующее, чтобы решить эту проблему:

Союз

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 запрашивающее симметрическая разность быть добавлены в качестве признака сердечника вместо того, чтобы сделать объединение двух заданных разностей.

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'}, 
       } 
    }, 

К сожалению, как представляется, не является хорошим способом объединить разнородные элементы из различных областей вместе. Чтобы получить союз, лучше всего сделать это от клиента. Или, если вы хотите фильтровать, делайте это по каждому набору отдельно.

+0

серьезно? Я указал вам на ответ на «союз», дал вам ответ для относительного дополнения и пересечения. есть ли необходимость в другом вопросе? –

+0

@ Ася, это не стук на тебя. Я дал вам кредит в моих комментариях выше. Причина, по которой я добавил это, заключалась в том, что другие были более конкретными. Первое, где вы хотите разобрать, где есть пересечение. Во втором случае вы помогли мне найти левое дополнение. Я думаю, что есть использование для другого вопроса, потому что другие были очень специфичны для использования. Я пытаюсь помочь сообществу, а не отнимать кредит от ответов, которые вы опубликовали. Этот вопрос лучше работает как тип вики сообщества. Вы должны быть счастливее, я помогаю вашей компании и оплачиваю ее. – Scott

+0

Когда это возможно, это тот тип вещей, который я бы прекомпостировал и сохранил. Я бы не захотел столкнуться с конвейером агрегации, подобным этому, в производственной системе. – WiredPrairie

Смежные вопросы