【问题标题】:Merging array fields in MongoDB aggregation在 MongoDB 聚合中合并数组字段
【发布时间】:2013-10-09 08:20:26
【问题描述】:

在使用 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" : []
}

聚合框架有没有一种巧妙的方法来实现这一点?

【问题讨论】:

  • Messages 是否保证以数组的形式存在?还是它可能不存在或将存在但作为不同的类型?
  • yes Messages 保证以数组的形式存在(对于某些记录可能为空)。
  • 你试过preserveNullAndEmptyArrays选项到$unwind吗?
  • 这个问题是在我们使用 v2.6 时提出的。我相信使用 preserveNullAndEmptyArrays 应该可以满足我们的需求。

标签: mongodb aggregation-framework


【解决方案1】:

如果 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”]。我不确定如何轻松摆脱这种情况下的“虚拟”。
  • 对 - 有办法摆脱它 - 我会更新答案
  • 好的,现在包含所有步骤的完整答案 - 应该正是您想要的 :)
  • @AsyaKamsky 谢谢,这是一个很大的帮助,你能帮我再做一个用例吗,因为我的文档中有两个数组字段,你可以说消息和标签。我需要两个字段的相同行为
  • 将其发布为包含完整详细信息的问题 - cmets 并不是真正用于讨论新问题。
【解决方案2】:

正如在其中一个 cmets 中已经提到的,对原始问题的最简单答案是将 preserveNullAndEmptyArrays 添加到 $unwind 阶段。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    相关资源
    最近更新 更多