2013-10-09 3 views
11

Можно ли объединить поля массива при использовании структуры агрегации MongoDB? Вот краткое изложение проблема, которую я пытаюсь решить:Слияние полей массива в агрегации MongoDB

входные выборки документов агрегирования:

{ 
    "Category" : 1, 
    "Messages" : ["Msg1", "Msg2"], 
    "Value" : 1 
}, 
{ 
    "Category" : 1, 
    "Messages" : [], 
    "Value" : 10 
}, 
{ 
    "Category" : 1, 
    "Messages" : ["Msg1", "Msg3"], 
    "Value" : 100 
}, 
{ 
    "Category" : 2, 
    "Messages" : ["Msg4"], 
    "Value" : 1000 
}, 
{ 
    "Category" : 2, 
    "Messages" : ["Msg5"], 
    "Value" : 10000 
}, 
{ 
    "Category" : 3, 
    "Messages" : [], 
    "Value" : 100000 
} 

Мы хотим группе «Категория» при подведении итогов «Value» и объединения «Сообщения». Я попробовал этот агрегацию трубопровод:

{group : { 
     _id : "$Category", 
     Value : { $sum : "$Value"}, 
     Messages : {$push : "$Messages"} 
    } 
}, 
{$unwind : "$Messages"}, 
{$unwind : "$Messages"}, 
{$group : { 
     _id : "$_id", 
     Value : {$first : "$Value"}, 
     Messages : {$addToSet : "$Messages"} 
    } 
} 

Результат является:

"result" : [{ 
     "_id" : 1, 
     "Value" : 111, 
     "Messages" : ["Msg3", "Msg2", "Msg1"] 
    }, 
    { 
     "_id" : 2, 
     "Value" : 11000, 
     "Messages" : ["Msg5", "Msg4"] 
    } 
] 

Однако это совершенно упускает категории 3, так как документы, в которых «Категория» является 3 не имеют каких-либо «Сообщения», и они отбрасываются вторым разворачиванием. Мы хотели бы, чтобы в результате также было включено следующее:

{ 
    "_id" : 3, 
    "Value" : 100000, 
    "Messages" : [] 
} 

Есть ли опрятный способ достижения этого по структуре агрегации?

+0

это сообщение гарантированно есть как массив? Или это возможно, что он не будет существовать или будет там, но как другой тип? –

+0

yes Сообщения гарантированно существуют как массив (который может быть пустым для некоторых записей). – etkarayel

+0

вы попробовали опцию 'preserveNullAndEmptyArrays' для' $ unwind'? –

ответ

12

Вот трюк, вы можете использовать, если сообщение гарантированно будет массивом:

> db.messages.find() 
    { "Category" : 1, "Messages" : [ "Msg1", "Msg2" ], "Value" : 1 } 
    { "Category" : 1, "Messages" : [ ], "Value" : 10 } 
    { "Category" : 1, "Messages" : [ "Msg1", "Msg3" ], "Value" : 100 } 
    { "Category" : 2, "Messages" : [ "Msg4" ], "Value" : 1000 } 
    { "Category" : 2, "Messages" : [ "Msg5" ], "Value" : 10000 } 
    { "Category" : 3, "Messages" : [ ], "Value" : 100000 } 

> var group1 = { 
    "$group": { 
     "_id":  "$Category", 
     "Value": { 
      "$sum":  "$Value" 
     }, 
     "Messages": { 
      "$push": "$Messages" 
     } 
    } 
}; 

> var project1 = { 
    "$project": { 
     "Value": 1, 
     "Messages": { 
      "$cond": [ 
       { 
        "$eq": [ 
         "$Messages", 
         [ [ ] ] 
        ] 
       }, 
       [ [ null ] ], 
       "$Messages" 
      ] 
     } 
    } 
}; 

> db.messages.aggregate(group1, project1) 
    { "_id" : 3, "Value" : 100000, "Messages" : [ [ null ] ] } 
    { "_id" : 2, "Value" : 11000, "Messages" : [ [ "Msg4" ], [ "Msg5" ] ] } 
    { "_id" : 1, "Value" : 111, "Messages" : [ [ "Msg1", "Msg2" ], [ ], [ "Msg1", "Msg3" ] ] } 

Теперь расслабьтесь дважды и перегруппировать, чтобы получить один массив Messages.

> var unwind = {"$unwind":"$Messages"}; 

> var group2 = { 
    $group: { 
     "_id":  "$_id", 
     "Value": { 
      "$first":  "$Value" 
     }, 
     "Messages": { 
      "$addToSet": "$Messages" 
     } 
    } 
}; 

> var project2 = { 
    "$project": { 
     "Category": "$_id", 
     "_id":  0, 
     "Value": 1, 
     "Messages": { 
      "$cond": [ 
       { 
        "$eq": [ 
         "$Messages", 
         [ null ] 
        ] 
       }, 
       [ ], 
       "$Messages" 
      ] 
     } 
    } 
}; 

> db.messages.aggregate(group1, project1, unwind, unwind, group2 ,project2) 
    { "Value" : 111, "Messages" : [ "Msg3", "Msg2", "Msg1" ], "Category" : 1 } 
    { "Value" : 11000, "Messages" : [ "Msg5", "Msg4" ], "Category" : 2 } 
    { "Value" : 100000, "Messages" : [ ], "Category" : 3 } 
+0

Спасибо за советы. Он почти делает то, что мне нужно. Однако есть случай, когда он не дает желаемого результата. Совокупный результат для Категории 1 (на основе документов в моем первоначальном сообщении) заканчивается тем, что у вас 4 сообщения: [«Msg1», «Msg2», «Msg3», «dummy»]. Я не уверен, как легко избавиться от «манекена» для этого случая. – etkarayel

+0

Правильно - есть способ избавиться от него - я обновлю ответ –

+0

ok, полный ответ теперь со всеми шагами - должен быть именно то, что вы хотите :) –

0

Вы можете попробовать выполнить запрос агрегации в версии 3.2.

db.messages.aggregate([ 
    {"$group":{"_id":"$Category","Value":{"$sum":"$Value"},"Messages":{"$push":"$Messages"}}}, 
    {"$unwind":{"path":"$Messages","preserveNullAndEmptyArrays":true}}, 
    {"$unwind":{"path":"$Messages","preserveNullAndEmptyArrays":true}}, 
    {"$group":{"_id":"$_id","Value":{"$first":"$Value"},"Messages":{"$addToSet":"$Messages"}}} 
]) 

Для 3.4 версии и за ее пределами

db.messages.aggregate([ 
    {"$group":{"_id":"$Category","Value":{"$sum":"$Value"},"Messages":{"$push":"$Messages"}}}, 
    {"$addFields":{ 
    "Messages":{ 
     "$reduce":{ 
     "input":"$Messages", 
     "initialValue":[], 
     "in":{"$setUnion":["$$value","$$this"]} 
     } 
    } 
    } 
    } 
]) 
Смежные вопросы