Để trả lời trực tiếp câu hỏi của bạn, có, đó là cách hiệu quả nhất. Nhưng tôi nghĩ chúng ta cần làm rõ lý do tại sao lại như vậy.
Như đã được đề xuất trong các lựa chọn thay thế, một điều mà mọi người đang xem xét là "sắp xếp" kết quả của bạn trước khi chuyển đến $group
và những gì họ đang xem là giá trị "dấu thời gian", vì vậy bạn muốn đảm bảo rằng mọi thứ đều theo thứ tự "dấu thời gian", do đó có dạng:
db.temperature.aggregate([
{ "$sort": { "station": 1, "dt": -1 } },
{ "$group": {
"_id": "$station",
"result": { "$first":"$dt"}, "t": {"$first":"$t"}
}}
])
Và như đã nói, tất nhiên bạn sẽ muốn một chỉ mục phản ánh điều đó để sắp xếp hiệu quả:
Tuy nhiên, và đây là điểm thực sự. Điều có vẻ như đã bị những người khác (nếu không phải đối với chính bạn bỏ qua) là tất cả dữ liệu này có thể đã được chèn đã theo thứ tự thời gian, trong đó mỗi lần đọc được ghi lại như đã thêm.
Vì vậy, vẻ đẹp của nó là _id
trường (với ObjectId
mặc định ) đã có trong thứ tự "dấu thời gian", vì bản thân nó thực sự chứa một giá trị thời gian và điều này làm cho câu lệnh có thể thực hiện được:
db.temperature.aggregate([
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"}, "t": {"$last":"$t"}
}}
])
Và nó là nhanh hơn. Tại sao? Bạn không cần phải chọn một chỉ mục (mã bổ sung để gọi ra), bạn cũng không cần phải "tải" chỉ mục ngoài tài liệu.
Chúng tôi đã biết các tài liệu được sắp xếp theo thứ tự (theo _id
) vì vậy $last
ranh giới là hoàn toàn hợp lệ. Bạn vẫn đang quét mọi thứ và bạn cũng có thể truy vấn "phạm vi" trên _id
các giá trị có giá trị như nhau giữa hai ngày.
Điều thực sự duy nhất cần nói ở đây là trong cách sử dụng "thế giới thực", việc $match
có thể thực tế hơn đối với bạn giữa các phạm vi ngày khi thực hiện loại tích lũy này thay vì nhận _id
"đầu tiên" và "cuối cùng" giá trị để xác định một "phạm vi" hoặc một cái gì đó tương tự trong cách sử dụng thực tế của bạn.
Vậy đâu là bằng chứng về điều này? Vâng, nó khá dễ dàng để tạo lại, vì vậy tôi chỉ làm như vậy bằng cách tạo một số dữ liệu mẫu:
var stations = [
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL",
"GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA",
"ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE",
"NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK",
"OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT",
"VA", "WA", "WV", "WI", "WY"
];
for ( i=0; i<200000; i++ ) {
var station = stations[Math.floor(Math.random()*stations.length)];
var t = Math.floor(Math.random() * ( 96 - 50 + 1 )) +50;
dt = new Date();
db.temperatures.insert({
station: station,
t: t,
dt: dt
});
}
Trên phần cứng của tôi (máy tính xách tay 8GB với đĩa spinny, không phải là xuất sắc, nhưng chắc chắn là đủ) khi chạy từng dạng câu lệnh cho thấy rõ ràng một khoảng dừng đáng chú ý với phiên bản sử dụng chỉ mục và sắp xếp (cùng các phím trên chỉ mục như câu lệnh sắp xếp). Nó chỉ là một khoảng dừng nhỏ, nhưng sự khác biệt đủ đáng kể để nhận thấy.
Ngay cả khi nhìn vào kết quả giải thích (phiên bản 2.6 trở lên, hoặc thực sự có trong 2.4.9 mặc dù không được ghi lại), bạn có thể thấy sự khác biệt ở đó, mặc dù $sort
được tối ưu hóa do sự hiện diện của một chỉ mục, thời gian thực hiện có vẻ là với việc lựa chọn chỉ mục và sau đó tải các mục đã được lập chỉ mục. Bao gồm tất cả các trường cho một "được bao phủ" truy vấn chỉ mục không có gì khác biệt.
Đối với bản ghi cũng vậy, việc lập chỉ mục hoàn toàn ngày và chỉ sắp xếp theo các giá trị ngày cho cùng một kết quả. Có thể nhanh hơn một chút, nhưng vẫn chậm hơn so với dạng chỉ mục tự nhiên không có sắp xếp.
Vì vậy, miễn là bạn có thể vui vẻ "phạm vi" trên đầu tiên và cuối cùng _id
thì đúng là sử dụng chỉ mục tự nhiên trên thứ tự chèn thực sự là cách hiệu quả nhất để làm điều này. Số dặm trong thế giới thực của bạn có thể thay đổi tùy thuộc vào việc điều này có phù hợp với bạn hay không và nó có thể đơn giản là thuận tiện hơn để triển khai chỉ mục và sắp xếp vào ngày.
Nhưng nếu bạn hài lòng với việc sử dụng _id
hoặc lớn hơn _id
"cuối cùng" trong truy vấn của bạn, sau đó có lẽ một tinh chỉnh để nhận các giá trị cùng với kết quả của bạn để trên thực tế, bạn có thể lưu trữ và sử dụng thông tin đó trong các truy vấn liên tiếp:
db.temperature.aggregate([
// Get documents "greater than" the "highest" _id value found last time
{ "$match": {
"_id": { "$gt": ObjectId("536076603e70a99790b7845d") }
}},
// Do the grouping with addition of the returned field
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"},
"t": {"$last":"$t"},
"lastDoc": { "$last": "$_id" }
}}
])
Và nếu bạn thực sự đang "theo dõi" các kết quả như vậy thì bạn có thể xác định giá trị lớn nhất của ObjectId
từ kết quả của bạn và sử dụng nó trong truy vấn tiếp theo.
Nhưng dù sao, hãy vui chơi với điều đó, nhưng một lần nữa Có, trong trường hợp này, truy vấn đó là cách nhanh nhất.