少なくとも最初の問題解決では、実際には集計フレームワークよりもmapReduceに適しています。集約フレームワークには、前のドキュメントの値、またはドキュメントの前の「グループ化された」値の概念がないため、これを実行できないのはこのためです。
一方、mapReduceには、処理中にステージとドキュメント間で共有できる「グローバルスコープ」があります。これにより、必要な1日の終わりの現在の残高の「現在の合計」が得られます。
db.collection.mapReduce(
function () {
var date = new Date(this.dateEntry.valueOf() -
( this.dateEntry.valueOf() % ( 1000 * 60 * 60 * 24 ) )
);
emit( date, this.amount );
},
function(key,values) {
return Array.sum( values );
},
{
"scope": { "total": 0 },
"finalize": function(key,value) {
total += value;
return total;
},
"out": { "inline": 1 }
}
)
これは日付のグループごとに合計され、[ファイナライズ]セクションで各日からの累積合計になります。
"results" : [
{
"_id" : ISODate("2015-01-06T00:00:00Z"),
"value" : 50
},
{
"_id" : ISODate("2015-01-07T00:00:00Z"),
"value" : 150
},
{
"_id" : ISODate("2015-01-09T00:00:00Z"),
"value" : 179
}
],
長期的には、毎日のエントリを含む個別のコレクションを用意し、 $inc
を使用して残高を変更することをお勧めします。 アップデートで。 $inc
も実行してください 毎日の初めにアップサートして、前日の残高を繰り越す新しいドキュメントを作成します。
// increase balance
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": amount } },
{ "upsert": true }
);
// decrease balance
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": -amount } },
{ "upsert": true }
);
// Each day
var lastDay = db.daily.findOne({ "dateEntry": lastDate });
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": lastDay.balance } },
{ "upsert": true }
);
これを行わない方法
元の記述から、集約フレームワークに導入された演算子が増えていることは事実ですが、ここで求められていることはまだ実用的ではありません 集計ステートメントで実行します。
同じ基本的なルールが適用され、集約フレームワークはできません 以前の「ドキュメント」からの値を参照することも、「グローバル変数」を格納することもできません。 「ハッキング」 これは、すべての結果を配列に強制することによるものです:
db.collection.aggregate([
{ "$group": {
"_id": {
"y": { "$year": "$dateEntry" },
"m": { "$month": "$dateEntry" },
"d": { "$dayOfMonth": "$dateEntry" }
},
"amount": { "$sum": "$amount" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" }
}},
{ "$addFields": {
"docs": {
"$map": {
"input": { "$range": [ 0, { "$size": "$docs" } ] },
"in": {
"$mergeObjects": [
{ "$arrayElemAt": [ "$docs", "$$this" ] },
{ "amount": {
"$sum": {
"$slice": [ "$docs.amount", 0, { "$add": [ "$$this", 1 ] } ]
}
}}
]
}
}
}
}},
{ "$unwind": "$docs" },
{ "$replaceRoot": { "newRoot": "$docs" } }
])
これは、パフォーマンスの高いソリューションでも、「安全」なでもありません。 より大きな結果セットは、16MBのBSON制限に違反する非常に現実的な確率を実行することを考慮してください。 「ゴールデンルール」として 、すべてのコンテンツを単一のドキュメントの配列内に配置することを提案するもの:
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" }
}}
それは基本的な欠陥であり、したがって解決策ではありません 。
結論
これを処理するためのはるかに決定的な方法は、通常、実行中の結果カーソルに対する後処理です。
var globalAmount = 0;
db.collection.aggregate([
{ $group: {
"_id": {
y: { $year:"$dateEntry"},
m: { $month:"$dateEntry"},
d: { $dayOfMonth:"$dateEntry"}
},
amount: { "$sum": "$amount" }
}},
{ "$sort": { "_id": 1 } }
]).map(doc => {
globalAmount += doc.amount;
return Object.assign(doc, { amount: globalAmount });
})
したがって、一般的には、次のことを行うことをお勧めします。
-
合計には、カーソルの反復と追跡変数を使用します。
mapReduce
サンプルは、上記の簡略化されたプロセスの考案された例です。 -
事前に集計された合計を使用します。おそらく、事前集計プロセスに応じて、カーソルの反復と協調して、それが単なる間隔の合計であるか、「繰り越された」現在の合計であるかは関係ありません。
集約フレームワークは、実際には「集約」にのみ使用する必要があります。必要な方法で処理するためだけに配列を操作するなどのプロセスを介してデータに強制を強制することは、賢明でも安全でもありません。最も重要なことは、クライアント操作コードがはるかにクリーンで効率的です。
「操作」はコードではるかにうまく処理されるので、データベースに得意なことをさせましょう。