Aby bezpośrednio odpowiedzieć na twoje pytanie, tak, jest to najskuteczniejszy sposób. Ale myślę, że musimy wyjaśnić, dlaczego tak jest.
Jak sugerowano w alternatywach, jedyną rzeczą, na którą ludzie patrzą, jest „sortowanie” wyników przed przejściem do $group
stage i to, na co patrzą, to wartość „timestamp”, więc chciałbyś się upewnić, że wszystko jest w kolejności „timestamp”, stąd forma:
db.temperature.aggregate([
{ "$sort": { "station": 1, "dt": -1 } },
{ "$group": {
"_id": "$station",
"result": { "$first":"$dt"}, "t": {"$first":"$t"}
}}
])
Jak już wspomniano, będziesz oczywiście chciał, aby indeks to odzwierciedlał, aby sortowanie było efektywne:
Jednak i to jest prawdziwy punkt. To, co wydaje się być pomijane przez innych (jeśli nie dla Ciebie), to fakt, że wszystkie te dane są prawdopodobnie wstawiane już w kolejności czasowej, w której każdy odczyt jest rejestrowany jako dodany.
Piękno tego polega na _id
pole ( z domyślnym ObjectId
) jest już w kolejności "timestamp", ponieważ sama w sobie zawiera wartość czasu, co umożliwia stwierdzenie:
db.temperature.aggregate([
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"}, "t": {"$last":"$t"}
}}
])
I to jest szybciej. Czemu? Cóż, nie musisz wybierać indeksu (dodatkowy kod do wywołania), nie musisz również „ładować” indeksu oprócz dokumentu.
Wiemy już, że dokumenty są w porządku ( przez _id
), więc $last
granice są całkowicie aktualne. Mimo wszystko skanujesz wszystko, a także możesz zapytać o „zakres” w _id
wartości są jednakowo ważne dla dwóch dat.
Jedyną prawdziwą rzeczą do powiedzenia jest to, że w użyciu „w prawdziwym świecie” może być po prostu bardziej praktyczne, aby $match
między zakresami dat podczas tego rodzaju akumulacji, w przeciwieństwie do pobierania „pierwszego” i „ostatniego” _id
wartości, aby zdefiniować „zakres” lub coś podobnego w twoim rzeczywistym użyciu.
Więc gdzie jest na to dowód? Cóż, jest to dość łatwe do odtworzenia, więc zrobiłem to, generując przykładowe dane:
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
});
}
Na moim sprzęcie (laptop 8 GB z dyskiem obrotowym, co nie jest wybitne, ale z pewnością wystarczające) uruchomienie każdej formy instrukcji wyraźnie pokazuje wyraźną przerwę z wersją za pomocą indeksu i sortowania (te same klawisze w indeksie, co instrukcja sort). To tylko drobna pauza, ale różnica jest na tyle znacząca, by ją zauważyć.
Nawet patrząc na dane wyjściowe wyjaśniania (wersja 2.6 i nowsze, lub faktycznie jest tam w wersji 2.4.9, chociaż nie jest to udokumentowane), można zauważyć w tym różnicę, chociaż $sort
jest zoptymalizowany ze względu na obecność indeksu, wydaje się, że czas poświęcony na wybór indeksu, a następnie ładowanie zindeksowanych wpisów. Uwzględnienie wszystkich pól dla "pokrytego" zapytanie indeksowe nie ma znaczenia.
Również w przypadku rekordu samo indeksowanie daty i sortowanie tylko według wartości dat daje ten sam wynik. Prawdopodobnie nieco szybszy, ale wciąż wolniejszy niż naturalny indeks bez sortowania.
Tak długo, jak możesz szczęśliwie „zasięgnąć” pierwszego i ostatni _id
wartości, to prawdą jest, że użycie naturalnego indeksu w zamówieniu reklamowym jest w rzeczywistości najskuteczniejszym sposobem, aby to zrobić. Twój przebieg w świecie rzeczywistym może się różnić w zależności od tego, czy jest to dla Ciebie praktyczne, czy nie, i może po prostu wygodniej zaimplementować indeks i sortować według daty.
Ale jeśli byłeś zadowolony z używania _id
zakresy lub większe niż „ostatni” _id
w zapytaniu, a następnie być może jedno ulepszenie, aby uzyskać wartości wraz z wynikami, dzięki czemu można faktycznie przechowywać i wykorzystywać te informacje w kolejnych zapytaniach:
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" }
}}
])
A jeśli faktycznie „śledziłeś” takie wyniki, możesz określić maksymalną wartość ObjectId
z wyników i użyj go w następnym zapytaniu.
W każdym razie baw się dobrze, ale znowu tak, w tym przypadku to zapytanie jest najszybszym sposobem.