Thay vì cố gắng buộc cơ sở dữ liệu trả về kết quả cho dữ liệu không tồn tại, cách tốt hơn là tạo dữ liệu trống bên ngoài truy vấn và hợp nhất kết quả vào chúng. Bằng cách đó, bạn có các mục nhập "0" của mình trong đó không có dữ liệu và cho phép cơ sở dữ liệu trả về những gì ở đó.
Hợp nhất là một quy trình cơ bản để tạo một bảng băm gồm các khóa duy nhất và chỉ cần thay thế bất kỳ giá trị nào được tìm thấy trong kết quả tổng hợp trong bảng băm đó. Trong JavaScript, một đối tượng cơ bản phù hợp cũng như tất cả các khóa là duy nhất.
Tôi cũng muốn thực sự trả về một Date
đối tượng từ kết quả tổng hợp bằng cách sử dụng toán học ngày để thao tác và "làm tròn" ngày thành khoảng thời gian cần thiết thay vì sử dụng các toán tử tổng hợp ngày. Bạn có thể điều chỉnh ngày tháng bằng cách sử dụng $subtract
để biến giá trị thành biểu diễn dấu thời gian số bằng cách trừ đi từ một ngày khác với giá trị ngày kỷ nguyên và $mod
toán tử để lấy phần còn lại và làm tròn ngày đến khoảng thời gian cần thiết.
Ngược lại, sử dụng $add
với một đối tượng ngày kỷ nguyên tương tự sẽ chuyển một giá trị số nguyên trở lại thành Ngày BSON. Và tất nhiên sẽ hiệu quả hơn nhiều nếu xử lý trực tiếp tới $group
thay vì sử dụng một $project
riêng biệt
vì bạn có thể chỉ cần xử lý các ngày đã sửa đổi trực tiếp vào nhóm _id
vẫn có giá trị.
Như một ví dụ về shell:
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] })
});
Điều này sẽ trả về tất cả các ngày trong khoảng thời gian kể cả 0
các giá trị không tồn tại dữ liệu, chẳng hạn như:
{ "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 }
Lưu ý rằng tất cả các giá trị "ngày tháng" thực sự vẫn là ngày tháng BSON, nhưng chỉ cần xâu chuỗi như vậy trong lần xuất hiện từ .printjson()
như một phương thức shell.
Một ví dụ ngắn gọn hơn có thể được hiển thị bằng cách sử dụng nodejs
nơi bạn có thể sử dụng các thao tác như async.parallel
để xử lý đồng thời cả cấu trúc băm và truy vấn tổng hợp cũng như một tiện ích hữu ích khác trong nedb
thực hiện "băm" bằng cách sử dụng các hàm quen thuộc với việc sử dụng bộ sưu tập MongoDB. Nó cũng cho thấy cách điều này có thể mở rộng quy mô cho các kết quả lớn bằng cách sử dụng bộ sưu tập MongoDB thực nếu bạn cũng đã thay đổi cách xử lý thành xử lý luồng của con trỏ trả về từ .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();
});
}
);
});
Điều này rất phù hợp với dữ liệu cho biểu đồ và đồ thị. Quy trình cơ bản giống nhau đối với bất kỳ triển khai ngôn ngữ nào và lý tưởng là được thực hiện trong quá trình xử lý song song để có hiệu suất tốt nhất, vì vậy môi trường không đồng bộ hoặc phân luồng mang lại cho bạn phần thưởng thực sự mặc dù đối với một mẫu nhỏ như thế này, bảng băm cơ bản có thể được tạo trong bộ nhớ rất nhanh chóng môi trường của bạn yêu cầu các hoạt động tuần tự.
Vì vậy, đừng thử và buộc cơ sở dữ liệu làm điều này. Chắc chắn có những ví dụ về các truy vấn SQL thực hiện việc "hợp nhất" này trên máy chủ cơ sở dữ liệu, nhưng nó chưa bao giờ thực sự là một ý tưởng tuyệt vời ở đó và thực sự nên được xử lý bằng quy trình hợp nhất "máy khách" tương tự vì nó chỉ tạo chi phí cơ sở dữ liệu mà thực sự không phải là ' t yêu cầu.
Tất cả đều rất hiệu quả và thiết thực đối với mục đích và tất nhiên nó không yêu cầu xử lý một truy vấn tổng hợp riêng biệt cho mỗi ngày trong khoảng thời gian, điều này sẽ không hiệu quả chút nào.