2013-10-09 4 views
11

È possibile unire campi array durante l'utilizzo del framework di aggregazione MongoDB? Qui è un problema di sintesi che sto cercando di risolvere:Unione di campi array nell'aggregazione MongoDB

documenti di input di esempio per l'aggregazione:

{ 
    "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 
} 

Vogliamo gruppo da 'categoria', mentre riassumendo 'Valore' e la fusione 'Messaggi'. Ho provato questo aggregazione oleodotto:

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

Il risultato è:

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

Tuttavia, questo manca completamente di categoria 3 dal momento che i documenti in cui 'Categoria' è di 3 non hanno alcun 'Messaggi' e sono lasciati cadere dal secondo svolgersi. Vorremmo che il risultato includesse anche quanto segue:

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

Esiste un modo efficace per raggiungere questo obiettivo dal quadro di aggregazione?

+0

è messaggi garantito di essere lì come un array? O è possibile che non esisterà o ci sarà ma come un tipo diverso? –

+0

sì È garantito che i messaggi esistano come array (che può essere vuoto per alcuni record). – etkarayel

+0

hai provato l'opzione 'preserveNullAndEmptyArrays' a' $ unwind'? –

risposta

12

Ecco un trucco è possibile utilizzare se i messaggi è garantito per essere un array:

> 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" ] ] } 

Ora si snodano due volte e ri-gruppo per ottenere un singolo array Messaggi.

> 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

Grazie per i suggerimenti. Fa quasi quello di cui ho bisogno. Tuttavia, c'è un caso in cui non produce il risultato desiderato. Il risultato aggregato per la categoria 1 (basato sui documenti nel mio post originale) ha 4 messaggi: ["Msg1", "Msg2", "Msg3", "dummy"]. Non sono sicuro di come eliminare facilmente "dummy" per questo caso. – etkarayel

+0

Giusto - c'è un modo per sbarazzarsi di esso - aggiornerò la risposta –

+0

ok, la risposta completa ora con tutti i passaggi - dovrebbe essere esattamente quello che vuoi :) –

0

È possibile provare sotto la query di aggregazione nella versione 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"}}} 
]) 

Per la versione 3.4 e oltre

db.messages.aggregate([ 
    {"$group":{"_id":"$Category","Value":{"$sum":"$Value"},"Messages":{"$push":"$Messages"}}}, 
    {"$addFields":{ 
    "Messages":{ 
     "$reduce":{ 
     "input":"$Messages", 
     "initialValue":[], 
     "in":{"$setUnion":["$$value","$$this"]} 
     } 
    } 
    } 
    } 
])