4

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

假设我每天存储人们之间的互动,并通过如下计数来跟踪 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 }
]

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

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

[更新]

所需的输出将是:

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

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

有什么提示吗?

谢谢

4

2 回答 2

5

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

是的,在 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
}
于 2012-12-16T11:37:43.467 回答
0

$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
}
于 2012-11-22T23:45:50.333 回答