4

複数の列にまたがる値でグループ化することは可能ですか?

人々の間のやり取りを日ごとに保存しているとしましょう。次のように、from と to をカウントして追跡します。

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'任意の 1 つとのすべての相互作用を取得できます。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 に答える