Chociaż powinno to być wyjaśnione w twoim pytaniu, próbka wyjściowa ze źródła sugeruje, że szukasz:
- Łączna liczba wiadomości na „uid”
- Wyraźna liczba wartości w „do”
- Wyraźna liczba wartości w „od”
- Podsumowanie zliczeń na „godzinę” dla każdego „uid”
Wszystko to jest możliwe za pomocą pojedynczej instrukcji agregacji i wystarczy ostrożne zarządzanie odrębnymi listami, a następnie trochę manipulacji, aby mapować wyniki dla każdej godziny w okresie 24 godzin.
Najlepszym podejściem są tutaj operatory wprowadzone w MongoDB 3.2:
db.collection.aggregate([
// First group by hour within "uid" and keep distinct "to" and "from"
{ "$group": {
"_id": {
"uid": "$uid",
"time": { "$hour": "$timestamp" }
},
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"count": { "$sum": 1 }
}},
// Roll-up to "uid" and keep each hour in an array
{ "$group": {
"_id": "$_id.uid",
"total": { "$sum": "$count" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": {
"$push": {
"index": "$_id.time",
"count": "$count"
}
}
}},
// Getting distinct "to" and "from" requires a double unwind of arrays
{ "$unwind": "$to" },
{ "$unwind": "$to" },
{ "$unwind": "$from" },
{ "$unwind": "$from" },
// And then adding back to sets for distinct
{ "$group": {
"_id": "$_id",
"total": { "$first": "$total" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": { "$first": "$temp_hours" }
}},
// Map out for each hour and count size of distinct lists
{ "$project": {
"count": "$total",
"from_count": { "$size": "$from" },
"to_count": { "$size": "$to" },
"hours": {
"$map": {
"input": [
00,01,02,03,04,05,06,07,08,09,10,11,
12,13,14,15,16,17,18,19,20,21,22,23
],
"as": "el",
"in": {
"$ifNull": [
{ "$arrayElemAt": [
{ "$map": {
"input": { "$filter": {
"input": "$temp_hours",
"as": "tmp",
"cond": {
"$eq": [ "$$el", "$$tmp.index" ]
}
}},
"as": "out",
"in": "$$out.count"
}},
0
]},
0
]
}
}
}
}},
// Optionally sort in "uid" order
{ "$sort": { "_id": 1 } }
])
Przed MongoDB 3.2 trzeba było trochę bardziej zaangażować się w mapowanie zawartości tablicy dla wszystkich godzin w ciągu dnia:
db.collection.aggregate([
// First group by hour within "uid" and keep distinct "to" and "from"
{ "$group": {
"_id": {
"uid": "$uid",
"time": { "$hour": "$timestamp" }
},
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"count": { "$sum": 1 }
}},
// Roll-up to "uid" and keep each hour in an array
{ "$group": {
"_id": "$_id.uid",
"total": { "$sum": "$count" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": {
"$push": {
"index": "$_id.time",
"count": "$count"
}
}
}},
// Getting distinct "to" and "from" requires a double unwind of arrays
{ "$unwind": "$to" },
{ "$unwind": "$to" },
{ "$unwind": "$from" },
{ "$unwind": "$from" },
// And then adding back to sets for distinct, also adding the indexes array
{ "$group": {
"_id": "$_id",
"total": { "$first": "$total" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": { "$first": "$temp_hours" },
"indexes": { "$first": { "$literal": [
00,01,02,03,04,05,06,07,08,09,10,11,
12,13,14,15,16,17,18,19,20,21,22,23
] } }
}},
// Denormalize both arrays
{ "$unwind": "$temp_hours" },
{ "$unwind": "$indexes" },
// Marry up the index entries and keep either the value or 0
// Note you are normalizing the double unwind to distinct index
{ "$group": {
"_id": {
"_id": "$_id",
"index": "$indexes"
},
"total": { "$first": "$total" },
"from": { "$first": "$from" },
"to": { "$first": "$to" },
"count": {
"$max": {
"$cond": [
{ "$eq": [ "$indexes", "$temp_hours.index" ] },
"$temp_hours.count",
0
]
}
}
}},
// Sort to keep index order - !!Important!!
{ "$sort": { "_id": 1 } },
// Put the hours into the array and get sizes for other results
{ "$group": {
"_id": "$_id._id",
"count": { "$first": "$total" },
"from_count": { "$first": { "$size": "$from" } },
"to_count": { "$first": { "$size": "$to" } },
"hours": { "$push": "$count" }
}},
// Optionally sort in "uid" order
{ "$sort": { "_id": 1 } }
])
Aby to rozbić, oba podejścia postępują zgodnie z tymi samymi podstawowymi krokami, z jedyną rzeczywistą różnicą występującą w mapowaniu „godzin” dla okresu 24 godzin.
W pierwszej agregacji $group
etap, celem jest uzyskanie wyników na godzinę obecnych w danych i dla każdej wartości „uid”. Prosty operator agregacji dat $hour
pomaga uzyskać tę wartość jako część klucza grupującego.
$addToSet
operacje są rodzajem „mini-grupy” same w sobie, co pozwala zachować „odrębne zestawy” dla każdej wartości „do” i „od”, przy jednoczesnym zasadniczo grupowaniu na godzinę.
Następna $grupa
jest bardziej „organizacyjny”, ponieważ zarejestrowane „liczby” dla każdej godziny są przechowywane w tablicy, podczas gdy wszystkie dane są pogrupowane według „uid”. Zasadniczo daje to wszystkie "dane", których naprawdę potrzebujesz do wyniku, ale oczywiście $addToSet
operacje tutaj to po prostu dodawanie "tablic w tablicach" różnych zestawów określonych na godzinę.
Aby uzyskać te wartości jako naprawdę odrębne listy dla każdego „uid” i tylko, konieczne jest zdekonstruowanie każdej tablicy za pomocą $unwind
a następnie w końcu zgrupuj z powrotem jako odrębne „zestawy”. Ten sam $addToSet
kompaktuje to, a $first
operacje po prostu pobierają „pierwsze” wartości innych pól, które są już takie same dla danych docelowych „per uid”. Jesteśmy z nich zadowoleni, więc zachowaj je takimi, jakie są.
Ostatnie etapy mają tutaj zasadniczo „kosmetyczny” charakter i można je w równym stopniu osiągnąć w kodzie po stronie klienta. Ponieważ nie ma danych dla każdego interwału godzinowego, należy je zmapować na tablicę wartości reprezentujących każdą godzinę. Te dwa podejścia różnią się w zależności od możliwości dostępnych operatorów między wersjami.
W wersji MongoDB 3.2 istnieją $filter
i $arrayElemAt
operatory, które efektywnie umożliwiają tworzenie logiki „transponowania” źródła wejściowego wszystkich możliwych pozycji indeksu ( 24 godziny ) na wartości, które są już określone dla liczebności z tych godzin w dostępnych danych. Jest to w zasadzie "bezpośrednie wyszukiwanie" wartości już zarejestrowanych dla każdej dostępnej godziny, aby sprawdzić, czy istnieje, gdzie to robi, liczba jest transponowana do pełnej tablicy. Tam, gdzie go nie ma, domyślną wartością jest 0
jest używany w miejscu.
Bez tych operatorów wykonanie tego „dopasowania” zasadniczo oznacza denormalizację obu tablic (zarejestrowanych danych i pełnych 24 pozycji) w celu porównania i transpozycji. To właśnie dzieje się w drugim podejściu z prostym porównaniem wartości „indeksów”, aby zobaczyć, czy był wynik dla tej godziny. $max
Operator jest tutaj używany głównie ze względu na dwa $unwind
zestawienia, w których każda zarejestrowana wartość z danych źródłowych będzie odtwarzana dla każdej możliwej pozycji indeksu. To "kompaktuje" tylko do pożądanych wartości na "godzinę indeksowania".
W tym drugim podejściu ważne staje się, aby $sort
w grupie _id
wartość. Dzieje się tak, ponieważ zawiera pozycję „indeks”, która będzie potrzebna podczas przenoszenia tej zawartości z powrotem do tablicy, która ma być uporządkowana. Co jest oczywiście ostatnią $grupą
etap tutaj, w którym uporządkowane pozycje są umieszczane w tablicy za pomocą $push
.
Wracając do „odrębnych list”, $size
Operator jest używany we wszystkich przypadkach do określenia „długości”, a tym samym „liczby” różnych wartości na listach „do” i „od”. Jest to co najmniej jedyne rzeczywiste ograniczenie w MongoDB 2.6, ale można je zastąpić po prostu „odwijaniem” każdej tablicy indywidualnie, a następnie grupowaniem z powrotem według _id
już obecne w celu zliczenia wpisów tablicy w każdym zestawie. To prosty proces, ale jak powinieneś zobaczyć $size
Operator jest tu lepszą opcją dla ogólnej wydajności.
Na koniec, twoje dane podsumowujące są nieco nieaktualne, ponieważ prawdopodobnie wpis z "ddd" w "from" miał być taki sam w "do", ale zamiast tego jest zapisany jako "bbb". Zmienia to odrębną liczbę trzeciej grupy „uid” dla „do” w dół o jeden wpis. Ale oczywiście logiczne wyniki, biorąc pod uwagę dane źródłowe, są rozsądne:
{ "_id" : 1000000, "count" : 3, "from_count" : 2, "to_count" : 2, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 2000000, "count" : 2, "from_count" : 1, "to_count" : 1, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 3000000, "count" : 5, "from_count" : 5, "to_count" : 4, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0 ] }
N.B Źródło zawiera również literówkę z ogranicznikiem wstawionym za pomocą :
zamiast przecinka zaraz po znaczniku czasu we wszystkich wierszach.