これには多くのことがあります。特に、集計 、しかしそれはできます 終わり。上場後の段階を説明します:
db.collection.aggregate([
// 1. Unwind both arrays
{"$unwind": "$win"},
{"$unwind": "$loss"},
// 2. Cast each field with a type and the array on the end
{"$project":{
"win.player": "$win.player",
"win.type": {"$cond":[1,"win",0]},
"loss.player": "$loss.player",
"loss.type": {"$cond": [1,"loss",0]},
"score": {"$cond":[1,["win", "loss"],0]}
}},
// Unwind the "score" array
{"$unwind": "$score"},
// 3. Reshape to "result" based on the value of "score"
{"$project": {
"result.player": {"$cond": [
{"$eq": ["$win.type","$score"]},
"$win.player",
"$loss.player"
] },
"result.type": {"$cond": [
{"$eq":["$win.type", "$score"]},
"$win.type",
"$loss.type"
]}
}},
// 4. Get all unique result within each document
{"$group": { "_id": { "_id":"$_id", "result": "$result" } }},
// 5. Sum wins and losses across documents
{"$group": {
"_id": "$_id.result.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$_id.result.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$_id.result.type","loss"]},1,0
]}}
}}
])
概要
これは、各「勝ち」と「負け」の配列の「プレーヤー」がすべて最初から一意であるという前提を前提としています。これは、ここでモデル化されているように見えるものには妥当であるように思われました:
-
両方のアレイを巻き戻します。これにより重複が作成されますが、後で削除されます。
-
投影する場合、 $cond が使用されます。 いくつかのリテラル文字列値を取得するための演算子(3値)。そして、最後の使用法は特別です。なぜなら、配列が追加されているからです。したがって、投影した後、その配列は再び巻き戻されます。より多くの重複が、それがポイントです。それぞれに1つの「勝ち」、1つの「負け」の記録。
-
$cond によるさらなる予測 演算子と
$eq の使用 オペレーターも。今回はマージします 2つのフィールドを1つに。したがって、これを使用すると、フィールドの「タイプ」が「スコア」の値と一致する場合、その「キーフィールド」が「結果」フィールドの値に使用されます。結果は、2つの異なる「win」フィールドと「loss」フィールドが同じ名前を共有し、「type」で識別されるようになりました。 -
各ドキュメント内の重複を取り除きます。ドキュメント
_id
でグループ化するだけです キーとしての「結果」フィールド。これで、元のドキュメントと同じ「勝ち」と「負け」のレコードが、配列から削除されたときとは異なる形式で表示されるはずです。 -
最後に、すべてのドキュメントをグループ化して、「プレーヤー」ごとの合計を取得します。 $cond のその他の使用法 および
$ eq ただし、今回は、現在のドキュメントが「勝ち」か「負け」かを判断します。したがって、これが一致する場合は1を返し、falseの場合は0を返します。これらの値は $ sum 「勝ち」と「負け」の合計数を取得するため。
そして、それは結果に到達する方法を説明しています。
集計演算子 の詳細 ドキュメントから。 $cond の「面白い」使用法の一部 そのリストでは、$に置き換えることができるはずです。文字通り オペレーター。ただし、バージョン2.6以降がリリースされるまでは利用できません。
MongoDB2.6以降の「簡略化された」ケース
もちろん、新しい集合演算子 があります これを書いている時点での今後のリリースは何ですか。これは、これをいくらか単純化するのに役立ちます。
db.collection.aggregate([
{ "$unwind": "$win" },
{ "$project": {
"win.player": "$win.player",
"win.type": { "$literal": "win" },
"loss": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id",
"loss": "$loss"
},
"win": { "$push": "$win" }
}},
{ "$unwind": "$_id.loss" },
{ "$project": {
"loss.player": "$_id.loss.player",
"loss.type": { "$literal": "loss" },
"win": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id._id",
"win": "$win"
},
"loss": { "$push": "$loss" }
}},
{ "$project": {
"_id": "$_id._id",
"results": { "$setUnion": [ "$_id.win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
しかし、「単純化」は議論の余地があります。私にとって、それは「ぐずぐずしている」ように「感じ」、より多くの仕事をしているだけです。 $に依存しているだけなので、確かに伝統的です。 setUnion マージする 配列の結果。
ただし、次に示すように、スキーマを少し変更すると、その「作業」は無効になります。
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
これにより、これまで行ってきたように「type」属性を追加して配列の内容を投影する必要がなくなり、クエリと実行される作業が減ります。
db.collection.aggregate([
{ "$project": {
"results": { "$setUnion": [ "$win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
そしてもちろん、次のようにスキーマを変更するだけです:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
それは物事を非常にします 簡単。そして、これは2.6より前のバージョンで行うことができます。だからあなたは今それをすることができます:
db.collection.aggregate([
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
したがって、私にとって、それが私のアプリケーションである場合、私はあなたが持っているものではなく、上記の最後の形式のスキーマが必要です。提供された集計操作で行われるすべての作業(最後のステートメントを除く)は、既存のスキーマ形式を取得してこれに操作することを目的としています。 フォームなので、上記のような単純な集計ステートメントを簡単に実行できます。
各プレイヤーは「勝ち/負け」属性で「タグ付け」されているため、いつでも「勝者/敗者」に個別にアクセスできます。
最後に。あなたの日付 文字列です。私はそれが好きではありません。
そうする理由があったかもしれませんが、私にはわかりません。 日でグループ化する必要がある場合 これは、適切なBSON日付を使用するだけで簡単に集約できます。そうすれば、他の時間間隔で簡単に作業できるようになります。
したがって、日付を修正して start_dateにした場合 、「duration」を end_timeに置き換えました 、それからあなたはあなたが簡単な数学によって「持続時間」を得ることができる何かを保つようになります+あなたはたくさんの余分なを得る 代わりにこれらを日付値として使用することでメリットが得られます。
だから、それはあなたにあなたのスキーマについて考えるためのいくらかの食べ物を与えるかもしれません。
興味のある方のために、ワーキングセットのデータを生成するために使用したコードを次に示します。
// Ye-olde array shuffle
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
for ( var l=0; l<10000; l++ ) {
var players = ["Player1","Player2","Player3","Player4"];
var playlist = shuffle(players);
for ( var x=0; x<playlist.length; x++ ) {
var obj = {
player: playlist[x],
score: Math.floor(Math.random() * (100000 - 50 + 1)) +50
};
playlist[x] = obj;
}
var rec = {
duration: Math.floor(Math.random() * (50000 - 15000 +1)) +15000,
date: new Date(),
win: playlist.slice(0,2),
loss: playlist.slice(2)
};
db.game.insert(rec);
}