Объединение полей массива в агрегации 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
}

Мы хотим группировать "Категория", суммируя "Значение" и объединяя "Сообщения". Я пробовал этот агрегатный конвейер:

{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" : []
}

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

Ответ 1

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

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

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

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

Ответ 2

Вы можете попробовать выполнить запрос агрегации в версии 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"]}
      }
    }
   }
  }
])