在MongoDB聚合中合并数组字段

19

在使用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
}

我们希望按'Category'分组,同时对'Value'求和并合并'Messages'。我尝试了以下聚合管道:

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

聚合框架有没有一种简洁的方法可以实现这个?


消息(Message)作为一个数组,它保证一定会存在吗?或者有没有可能不存在或以不同的类型存在? - Asya Kamsky
是的,Messages保证存在为数组(对于某些记录可能为空)。 - etkarayel
1
你尝试过在 $unwind 中使用 preserveNullAndEmptyArrays 选项吗? - tony_k
当我们使用v2.6时,就提出了这个问题。我相信使用preserveNullAndEmptyArrays应该可以实现我们想要的效果。 - etkarayel
2个回答

18

如果Messages被保证是一个数组,那么您可以使用以下技巧:

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

谢谢你的建议。它几乎满足了我的需求。然而,有一种情况下它没有产生期望的结果。基于我原帖中的文档,类别1的聚合结果最终包含了4条消息:["Msg1", "Msg2", "Msg3", "dummy"]。对于这种情况,我不确定如何轻松地去掉"dummy"。 - etkarayel
没错 - 有一种方法可以摆脱它 - 我会更新答案。 - Asya Kamsky
好的,完整的答案现在包含所有步骤 - 应该正是你想要的 :) - Asya Kamsky
@AsyaKamsky 谢谢,这很有帮助。你可以再帮我一下吗?我的文档中有两个数组字段,一个是消息,一个是标签。我需要对这两个字段采取相同的行为。 - viren
将其作为问题发布,附带完整细节-评论不适合讨论新问题。 - Asya Kamsky

3

如在评论中已经提到的那样,对于原始问题,最简单的答案是在 $unwind 阶段添加 preserveNullAndEmptyArrays。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接