Zamiast próbować zmusić bazę danych do zwracania wyników dla danych, które nie istnieją, lepszą praktyką jest generowanie pustych danych zewnętrznych względem zapytania i scalanie wyników z nimi. W ten sposób masz swoje wpisy "0" tam, gdzie nie ma danych i pozwalasz bazie danych na zwrócenie tego, co tam jest.
Scalanie to podstawowy proces tworzenia zaszyfrowanej tabeli unikalnych kluczy i proste zastąpienie dowolnych wartości znalezionych w agregacji w tej tabeli zaszyfrowanej. W JavaScript podstawowy obiekt pasuje, a wszystkie klucze są unikalne.
Wolę również zwracać Date
obiekt z wyników agregacji przy użyciu matematyki daty do manipulowania i „zaokrąglania” daty do wymaganego interwału, zamiast używania operatorów agregacji dat. Możesz manipulować datami, używając $subtract
aby przekształcić wartość w liczbową reprezentację znacznika czasu, odejmując od innej daty wartość daty epoki oraz $mod
operatora, aby uzyskać resztę i zaokrąglić datę do wymaganego przedziału.
W przeciwieństwie do tego, używając $add
z podobnym obiektem daty epoki zamieni wartość całkowitą z powrotem na datę BSON. I oczywiście znacznie bardziej wydajne jest przetwarzanie bezpośrednio do $group
zamiast używać oddzielnego $project
etap, ponieważ możesz po prostu przetworzyć zmodyfikowane daty bezpośrednio w grupie _id
wartość mimo wszystko.
Jako przykład powłoki:
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] })
});
Który zwróci wszystkie dni w przedziale, w tym 0
wartości, w przypadku których nie ma danych, np.:
{ "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 }
Zauważając, że wszystkie wartości "dat" są w rzeczywistości nadal datami BSON, ale po prostu skróć w ten sposób w danych wyjściowych z .printjson()
jako metoda powłoki.
Nieco bardziej zwięzły przykład można pokazać za pomocą nodejs
gdzie możesz używać operacji takich jak async.parallel
do przetwarzania zarówno konstrukcji skrótu, jak i zapytania agregującego w tym samym czasie, a także innego użytecznego narzędzia w nedb
który implementuje "hash" używając funkcji znanych z używania kolekcji MongoDB. Pokazuje również, jak można to skalować dla dużych wyników, używając prawdziwej kolekcji MongoDB, jeśli zmieniłeś również obsługę przetwarzania strumieniowego zwracanego kursora z .aggregate()
:
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();
});
}
);
});
Jest to bardzo odpowiednie dla danych do wykresów i wykresów. Podstawowa procedura jest taka sama dla każdej implementacji języka i najlepiej jest przeprowadzana w przetwarzaniu równoległym w celu uzyskania najlepszej wydajności, więc środowiska asynchroniczne lub wątkowe dają prawdziwą premię, nawet jeśli w przypadku takiej małej próbki podstawowa tablica mieszająca może być bardzo szybko wygenerowana w pamięci twojego środowiska wymaga sekwencyjnych operacji.
Więc nie próbuj zmuszać bazy danych do tego. Z pewnością istnieją przykłady zapytań SQL, które wykonują to „scalanie” na serwerze bazy danych, ale nigdy nie było to naprawdę świetnym pomysłem i naprawdę powinno być obsługiwane za pomocą podobnego procesu scalania „klienta”, ponieważ jest to tylko tworzenie narzutu bazy danych, który tak naprawdę nie jest wymagane.
Wszystko to jest bardzo wydajne i praktyczne w tym celu i oczywiście nie wymaga przetwarzania oddzielnego zapytania agregującego dla każdego dnia w okresie, co w ogóle nie byłoby wydajne.