Selvom det burde have været gjort mere klart i dit spørgsmål, tyder dit outputeksempel fra kilden på, at du leder efter:
- Samlet antal meddelelser pr. "uid"
- Særskilt antal værdier i "til"
- Særskilt antal værdier i "fra"
- Oversigt over antal pr. "time" for hver "uid"
Dette er alt sammen muligt i en enkelt aggregeringserklæring, og det kræver bare lidt omhyggelig styring af de forskellige lister og derefter noget manipulation for at kortlægge resultater for hver time i en 24-timers periode.
Den bedste tilgang her er hjulpet af operatører introduceret i 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 } }
])
Før MongoDB 3.2 skal du involvere dig lidt mere for at kortlægge array-indholdet for alle timer i døgnet:
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 } }
])
For at nedbryde det, følger begge tilgange her de samme grundlæggende trin, hvor den eneste reelle forskel forekommer på kortlægningen af "timer" for 24 timers perioden.
I den første sammenlægning $group
trin er målet at få resultater pr. time til stede i dataene og for hver "uid"-værdi. Den simple datosammenlægningsoperator for $hour
hjælper med at opnå denne værdi som en del af grupperingsnøglen.
$addToSet
operationer er en slags "mini-gruppe" i sig selv, og dette gør det muligt at beholde de "adskilte sæt" for hver af "til" og "fra" værdierne, mens de i det væsentlige stadig grupperes pr. time.
Den næste $group
er mere "organisatorisk", da de registrerede "tæller" for hver time opbevares i et array, mens alle data rulles op til blot at blive grupperet efter "uid". Dette giver dig dybest set alle de "data", du virkelig har brug for til resultatet, men selvfølgelig $addToSet
operationer her tilføjer bare "arrays inden for arrays" af de forskellige sæt bestemt pr. time.
For at få disse værdier som virkelig adskilte lister pr. hver "uid" og kun, er det nødvendigt at dekonstruere hvert array ved hjælp af $unwind
og så til sidst gruppere tilbage som blot de distinkte "sæt". Den samme $addToSet
komprimerer dette, og $first
operationer tager bare de "første" værdier af de andre felter, som allerede er de samme for mål "per uid" data. Vi er glade for dem, så behold dem bare som de er.
De(n) sidste fase(r) her er i det væsentlige af "kosmetisk" natur og kan ligeledes opnås i klientsidekode. Da der ikke er data til stede for hvert enkelt timeinterval, skal det kortlægges i en række værdier, der repræsenterer hver time. De to tilgange her varierer afhængigt af de tilgængelige operatørers muligheder mellem versioner.
I MongoDB 3.2-udgivelsen er der $filter
og $arrayElemAt
operatører, der effektivt giver dig mulighed for at skabe logikken til at "transponere" en inputkilde for alle mulige indekspositioner (24 timer) til de værdier, der allerede er bestemt for tællingerne fra disse timer i de tilgængelige data. Dette er grundlæggende et "direkte opslag" af værdier, der allerede er registreret for hver tilgængelig time for at se, om den eksisterer, hvor det sker, transponeres optællingen til det fulde array. Hvor den ikke er til stede, er standardværdien 0
bruges på plads.
Uden disse operatører betyder dette "match up" i det væsentlige at denormalisere begge arrays (de registrerede data og de fulde 24 positioner) for at sammenligne og transponere. Dette er, hvad der sker i den anden tilgang med en simpel sammenligning af "indeks"-værdierne for at se, om der var et resultat for den time. $max
operatoren her bruges hovedsageligt på grund af de to $unwind
udsagn, hvor hver registreret værdi fra kildedataene vil blive gengivet for enhver mulig indeksposition. Dette "komprimerer" til kun de værdier, der ønskes pr. "indekstime".
I den sidstnævnte tilgang bliver det så vigtigt at $sort
på grupperingen _id
værdi. Dette er fordi det indeholder "indeks"-positionen, og det vil være nødvendigt, når du flytter dette indhold tilbage til et array, du forventer at blive bestilt. Hvilket selvfølgelig er den sidste $group
trin her, hvor de ordnede positioner sættes i et array med $push
.
Tilbage til de "adskilte lister", $size
operator bruges i alle tilfælde til at bestemme "længden" og derfor "antal" af distinkte værdier i listerne for "til" og "fra". Dette er i det mindste den eneste reelle begrænsning på MongoDB 2.6, men kan ellers erstattes med blot at "afvikle" hvert array individuelt og derefter gruppere tilbage på _id
allerede til stede for at tælle array-indgange i hvert sæt. Det er en grundlæggende proces, men som du burde se $size
operatør er den bedre mulighed her for den samlede ydeevne.
Som en sidste bemærkning er dine konklusionsdata lidt ude, da det muligvis var meningen, at indtastningen med "ddd" i "fra" også skulle være den samme i "til", men i stedet registreres som "bbb". Dette ændrer det distinkte antal af den tredje "uid"-gruppering for "til" ned med én indgang. Men selvfølgelig er de logiske resultater givet kildedataene sunde:
{ "_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. Kilden har også en tastefejl, hvor afgrænseren er indskudt med :
i stedet for et komma lige efter tidsstemplet på alle linjer.