存在しないデータの結果をデータベースに強制的に返さないようにするのではなく、クエリの外部で空白のデータを生成し、結果をそれらにマージすることをお勧めします。このようにして、データがない「0」エントリがあり、データベースがそこにあるものを返すことができます。
マージは、一意のキーのハッシュテーブルを作成し、そのハッシュテーブルの集計結果で見つかった値を単純に置き換える基本的なプロセスです。 JavaScriptでは、基本的なオブジェクトが適しているだけでなく、すべてのキーが一意です。
また、実際にDate
を返すことを好みます 日付集計演算子を使用するのではなく、日付計算を使用して日付を操作し、必要な間隔に「丸める」ことにより、集計結果からオブジェクトを取得します。 $subtract
を使用して、日付を操作できます。
別の日付からエポック日付値を減算して値を数値のタイムスタンプ表現に変換し、 $mod
演算子を使用して余りを取得し、日付を必要な間隔に丸めます。
対照的に、 $add
同様のエポック日付オブジェクトを使用すると、整数値がBSON日付に戻ります。そしてもちろん、$group
別の$project
を使用するのではなく
変更された日付をグループ化_id
に直接処理できるためステージ とにかく価値があります。
シェルの例として:
var sample = 30,
Days = 30,
OneDay = ( 1000 * 60 * 60 * 24 ),
now = Date.now(),
Today = now - ( now % OneDay ) ,
nDaysAgo = Today - ( OneDay * Days ),
startDate = new Date( nDaysAgo ),
endDate = new Date( Today + OneDay ),
store = {};
var thisDay = new Date( nDaysAgo );
while ( thisDay < endDate ) {
store[thisDay] = 0;
thisDay = new Date( thisDay.valueOf() + OneDay );
}
db.datejunk.aggregate([
{ "$match": { "when": { "$gte": startDate } }},
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$when", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$when", new Date(0) ] },
OneDay
]}
]},
new Date(0)
]
},
"count": { "$sum": 1 }
}}
]).forEach(function(result){
store[result._id] = result.count;
});
Object.keys(store).forEach(function(k) {
printjson({ "date": k, "count": store[k] })
});
0
を含む間隔のすべての日を返します 次のようなデータが存在しない値:
{ "date" : "Tue Sep 22 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Wed Sep 23 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Thu Sep 24 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Fri Sep 25 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Sat Sep 26 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Sun Sep 27 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Mon Sep 28 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Tue Sep 29 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Wed Sep 30 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Thu Oct 01 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Fri Oct 02 2015 10:00:00 GMT+1000 (AEST)", "count" : 2 }
{ "date" : "Sat Oct 03 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Sun Oct 04 2015 11:00:00 GMT+1100 (AEST)", "count" : 1 }
{ "date" : "Mon Oct 05 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 06 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Wed Oct 07 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 08 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Fri Oct 09 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Sat Oct 10 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Sun Oct 11 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Mon Oct 12 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 13 2015 11:00:00 GMT+1100 (AEDT)", "count" : 3 }
{ "date" : "Wed Oct 14 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 15 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Fri Oct 16 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Sat Oct 17 2015 11:00:00 GMT+1100 (AEDT)", "count" : 3 }
{ "date" : "Sun Oct 18 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Mon Oct 19 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 20 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Wed Oct 21 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 22 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
すべての「日付」値は実際にはまだBSON日付であることに注意してください。ただし、.printjson()
からの出力でそのように文字列化してください。 シェルメソッドとして。
nodejs
を使用して、もう少し簡潔な例を示すことができます。 async.parallel
などの操作を利用できる場所
ハッシュ構築と集計クエリの両方を同時に処理するため、およびnedb
これは、MongoDBコレクションの使用に慣れている関数を使用して「ハッシュ」を実装します。また、.aggregate()
から返されたカーソルの処理をストリーム処理に変更した場合に、実際のMongoDBコレクションを使用してこれが大きな結果にどのようにスケーリングできるかを示します。 :
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient,
nedb = require('nedb'),
DataStore = new nedb();
// Setup vars
var sample = 30,
Days = 30,
OneDay = ( 1000 * 60 * 60 * 24 ),
now = Date.now(),
Today = now - ( now % OneDay ) ,
nDaysAgo = Today - ( OneDay * Days ),
startDate = new Date( nDaysAgo ),
endDate = new Date( Today + OneDay );
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection('datejunk');
async.series(
[
// Clear test collection
function(callback) {
coll.remove({},callback)
},
// Generate a random sample
function(callback) {
var bulk = coll.initializeUnorderedBulkOp();
while (sample--) {
bulk.insert({
"when": new Date(
Math.floor(
Math.random()*(Today-nDaysAgo+OneDay)+nDaysAgo
)
)
});
}
bulk.execute(callback);
},
// Aggregate data and dummy data
function(callback) {
console.log("generated");
async.parallel(
[
// Dummy data per day
function(callback) {
var thisDay = new Date( nDaysAgo );
async.whilst(
function() { return thisDay < endDate },
function(callback) {
DataStore.update(
{ "date": thisDay },
{ "$inc": { "count": 0 } },
{ "upsert": true },
function(err) {
thisDay = new Date( thisDay.valueOf() + OneDay );
callback(err);
}
);
},
callback
);
},
// Aggregate data in collection
function(callback) {
coll.aggregate(
[
{ "$match": { "when": { "$gte": startDate } } },
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$when", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$when", new Date(0) ] },
OneDay
]}
]},
new Date(0)
]
},
"count": { "$sum": 1 }
}}
],
function(err,results) {
if (err) callback(err);
async.each(results,function(result,callback) {
DataStore.update(
{ "date": result._id },
{ "$inc": { "count": result.count } },
{ "upsert": true },
callback
);
},callback);
}
);
}
],
callback
);
}
],
// Return result or error
function(err) {
if (err) throw err;
DataStore.find({},{ "_id": 0 })
.sort({ "date": 1 })
.exec(function(err,results) {
if (err) throw err;
console.log(results);
db.close();
});
}
);
});
これは、チャートやグラフのデータに非常に適しています。基本的な手順はどの言語実装でも同じであり、最高のパフォーマンスを得るために並列処理で実行するのが理想的です。したがって、このような小さなサンプルの場合、基本的なハッシュテーブルをメモリ内で非常に迅速に生成できますが、非同期環境またはスレッド環境では大きなメリットがあります。環境の一部には、順次操作が必要です。
したがって、データベースにこれを強制しようとしないでください。データベースサーバー上でこの「マージ」を行うSQLクエリの例は確かにありますが、実際には素晴らしいアイデアではなく、データベースのオーバーヘッドを作成するだけなので、同様の「クライアント」マージプロセスで処理する必要があります。 tが必要です。
それはすべて非常に効率的で目的に合った実用的であり、もちろん、期間内の日ごとに個別の集計クエリを処理する必要はありません。これはまったく効率的ではありません。