【问题标题】:Combining columns into one in MongoDB Aggregate Framework在 MongoDB Aggregate Framework 中将列合并为一个
【发布时间】:2012-11-22 23:20:58
【问题描述】:

是否可以跨多列按值分组?

假设我每天存储人们之间的互动,并使用如下计数来跟踪 from's 和 to's。

db.collection = 
[
    { from : 'bob',   to : 'mary',   day : 1,  count : 2 },
    { from : 'bob',   to : 'steve',  day : 2,  count : 1 },
    { from : 'mary',  to : 'bob',    day : 1,  count : 3 },
    { from : 'mary',  to : 'steve',  day : 3,  count : 1 },
    { from : 'steve', to : 'bob',    day : 2,  count : 2 },
    { from : 'steve', to : 'mary',   day : 1,  count : 1 }
]

这使我可以通过对from: 进行分组并对count: 求和来获得'bob' 与任何人的所有交互。

现在我想获得用户的所有交互,所以基本上按 from:to: 的值分组。本质上,为每个名字总结count:,不管它是在from:还是to:

[更新]

期望的输出是:

[
    { name : 'bob',   count : 8 },
    { name : 'mary',  count : 7 },
    { name : 'steve', count : 3 }
]

最简单的方法是创建一个新列names: 并在其中存储from:to:,然后是$unwind,但这似乎很浪费。

有什么提示吗?

谢谢

【问题讨论】:

  • 谢谢。用示例输出更新了我的问题。
  • 首先,应该是 name: 'steve', count: 5 在所需的输出中,对吗?您可以在聚合框架中执行此操作而无需更改架构(虽然它并不漂亮)。

标签: mongodb aggregation-framework


【解决方案1】:

是否可以跨多列按值分组?

是的,在 MongoDB 中可以跨不同列对值进行分组。

通过 MapReduce 非常简单。但是也可以使用聚合框架来做到这一点,即使您不存储参与者数组(如果您有两个参与者的名称数组,那么它只是一个 $unwind 和一个 $group - 很简单,我认为比 MapReduce 或您必须与当前模式一起使用的管道更优雅)。

按原样与您的架构一起工作的管道:

db.collection.aggregate( [
{
    "$group" : {
        "_id" : "$from",
        "sum" : {
            "$sum" : "$count"
        },
        "tos" : {
            "$push" : {
                "to" : "$to",
                "count" : "$count"
            }
        }
    }
}
{ "$unwind" : "$tos" }
{
    "$project" : {
        "prev" : {
            "id" : "$_id",
            "sum" : "$sum"
        },
        "tos" : 1
    }
}
{
    "$group" : {
        "_id" : "$tos.to",
        "count" : {
            "$sum" : "$tos.count"
        },
        "prev" : {
            "$addToSet" : "$prev"
        }
    }
}
{ "$unwind" : "$prev" }
{
    "$group" : {
        "_id" : "1",
        "t" : {
            "$addToSet" : {
                "id" : "$_id",
                "c" : "$count"
            }
        },
        "f" : {
            "$addToSet" : {
                "id" : "$prev.id",
                "c" : "$prev.sum"
            }
        }
    }
}
{ "$unwind" : "$t" }
{ "$unwind" : "$f" }
{
    "$project" : {
        "name" : {
            "$cond" : [
                {
                    "$eq" : [
                        "$t.id",
                        "$f.id"
                    ]
                },
                "$t.id",
                "nobody"
            ]
        },
        "count" : {
            "$add" : [
                "$t.c",
                "$f.c"
            ]
        },
        "_id" : 0
    }
}
{ "$match" : { "name" : { "$ne" : "nobody" } } }
]);

在您的示例输入中,输出为:

{
    "result" : [
        {
            "name" : "bob",
            "count" : 8
        },
        {
            "name" : "mary",
            "count" : 7
        },
        {
            "name" : "steve",
            "count" : 5
        }
    ],
    "ok" : 1
}

【讨论】:

    【解决方案2】:

    $unwind 可能很昂贵。这不是更容易查询吗?

    db.collection = 
    [
        { name : 'bob',   to : 'mary',   day : 1,  count : 2 },
        { name : 'mary',  from : 'bob',  day : 1,  count : 2 },
        { name : 'bob',   to : 'steve',  day : 2,  count : 1 },
        { name : 'bob',   from : 'steve',day : 2,  count : 1 },
        { name : 'mary',  to : 'bob',    day : 1,  count : 3 },
        { name : 'mary',  from : 'bob',  day : 1,  count : 3 },
        { name : 'mary',  to : 'steve',  day : 3,  count : 1 },
        { name : 'mary',  from : 'steve' day : 3,  count : 1 },
        { name : 'steve', to : 'bob',    day : 2,  count : 2 },
        { name : 'steve', from : 'bob',  day : 2,  count : 2 },
        { name : 'steve', to : 'mary',   day : 1,  count : 1 }
        { name : 'steve', from : 'mary', day : 1,  count : 1 }
    ]
    

    [更新]

    使用您现有的结构,您可以使用 Map-Reduce 执行此操作,但这并不是真正的实时结果。总体上它会更慢,但可能比 AF 中的大规模 $unwind 操作更有效;

    db.so.drop();
    db.so.insert(
    [
        { from: 'bob', to: 'mary', day: 1, count: 2 },
        { from: 'bob', to: 'steve', day: 2, count: 1 },
        { from: 'mary', to: 'bob', day: 1, count: 3 },
        { from: 'mary', to: 'steve', day: 3, count: 1 },
        { from: 'steve', to: 'bob', day: 2, count: 2 },
        { from: 'steve', to: 'mary', day: 1, count: 1 }
    ]);
    
    db.runCommand(
        {
            "mapreduce": "so", // don't need the collection name here if it's above
            "map": function(){
                emit(this.from, {count: this.count});
                emit(this.to, {count: this.count});
            },
            "reduce": function (name, values) {
                var result = { count: 0 };
                values.forEach(function (v) {
                    result.count += v.count;
                });
    
                return result;
            },
            query: {},
            out: { inline: 1 },
        }
    );
    

    产生;

    {
        "results" : [
                {
                    "_id" : "bob",
                    "value" : {
                        "count" : 8
                    }
                },
                {
                    "_id" : "mary",
                    "value" : {
                        "count" : 7
                    }
                },
                {
                    "_id" : "steve",
                    "value" : {
                        "count" : 5
                    }
                }
        ],
        "timeMillis" : 1,
        "counts" : {
            "input" : 6,
            "emit" : 12,
            "reduce" : 3,
            "output" : 3
        },
            "ok" : 1
    }
    

    【讨论】:

    • 是的,可以,但是我们不这样存储东西,有一些重复的记录,我宁愿不转换数据模型。不过,这将是最后的手段。
    • 我看不出有一种方法可以使用具有您现有结构的 AF 来执行此操作,但您可以使用 Map-Reduce 来执行此操作,假设您不需要实时的按需结果?
    • 除此之外,下一个最简单的方法可能是使用 AF 对数据进行两次传递,并将每次传递的“from”和“to”的总和结果相加。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多