Wer hat gewonnen? Ranking in der Aggregation Pipeline
Die $setWindowFields
-Stage verfügt über verschiedene Möglichkeiten, die Position eines Werts im Vergleich zu anderen Dokumenten zu bestimmen. Ausgehend von folgenden Demodaten werfen wir einen Blick auf die Unterschiede:
[{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100
},
{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10
},
{
"ts": ISODate("2023-09-27T00:00:00.000Z"),
"value": 100
}]
Wichtig ist beim Ranking die Sortierung der Dokumente; während in anderen Fällen oft der Zeitstempel verwendet wird, wird beim Ranking nach dem Platzierungskriterium sortiert:
[
{
$setWindowFields: {
partitionBy: null,
sortBy: { value: -1 },
output: {
position: {
$documentNumber: {},
},
},
},
},
]
Obiges Beispiel sortiert die Dokumente absteigend nach dem Wert und verwendet die $documentNumber
-Funktion, um die Dokumente zu nummerieren. Aufgrund der Einfachheit der Funktion werden auch keine weiteren Parameter angegeben. Bei mehrfach vorkommenden Werten wird die Nummerierung einfach fortgesetzt.
Dies führt zu folgendem Ergebnis
[{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"position": 1
},
{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"position": 2
},
{
"ts": ISODate("2023-09-27T00:00:00.000Z"),
"value": 100,
"position": 3
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"position": 4
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"position": 5
}]
$rank und $denseRank
Will man eine Platzvergabe, bei denen gleiche Werte zur gleichen Platzierung führen, kann man die Funktionen $rank
oder $denseRank
nutzen. Beide Funktionen vergeben für gleiche Werte die gleiche Platzierung. Der Unterschied ist, wie die Nummerierung anschließend fortgesetzt wird:
-
$rank
lässt anschließend Lücken. -
$denseRank
setzt die Nummerierung anschließend ohne Lücken fort.
In einer Aggregation-Pipeline werden die Funktionen folgendermaßen eingesetzt ($rank
könnte ohne weiteres durch $denseRank
ersetzt werden, falls gewünscht):
[
{
$setWindowFields: {
partitionBy: null,
sortBy: { value: -1 },
output: {
position: {
$rank: {},
},
},
},
},
]
Das Ergebnis bei Einsatz von $rank
ist folgendes:
[{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"position": 1
},
{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"position": 2
},
{
"ts": ISODate("2023-09-27T00:00:00.000Z"),
"value": 100,
"position": 2
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"position": 4
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"position": 5
}]
$denseRank
hingegen vergibt die Position 3 und 4 im Anschluss an die beiden zweiten Plätze:
[{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"position": 1
},
{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"position": 2
},
{
"ts": ISODate("2023-09-27T00:00:00.000Z"),
"value": 100,
"position": 2
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"position": 3
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"position": 4
}]
Und welcher ist der schlechteste Wert?
Mit obiger Pipeline sind wir in der Lage, die Position eines Werts zu bestimmen. Die Bestimmung des besten Werts ist also kein Problem. Wollen wir zusätzlich markieren können, wenn ein Wert der schlechteste war, dann können wir das durch die folgende Ergänzung der Aggregation Pipeline erreichen:
[
{
$setWindowFields: {
partitionBy: null,
sortBy: { value: -1 },
output: {
position: {
$denseRank: {},
},
worst: {
$last: "$value",
window: {
documents: ["unbounded", "unbounded"],
},
},
},
},
},
{
$set: {
isWorst: {
$eq: ["$value", "$worst"],
},
},
},
{
$unset: "worst"
}
]
Die Pipeline bestimmt in der $setWindowFields
-Stage neben der Platzierung über $denseRank
auch den schlechtesten Wert. In der folgenden $set
-Stage wird anschließend der aktuelle Wert mit dem schlechtesten verglichen und das Ergebnis im isWorst
-Feld abgespeichert. Dieser Ansatz funktioniert auch in Szenarien, in denen es mehrere Dokumente mit den schlechtesten Werten gibt und führt zu folgender Ausgabe:
[{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"position": 1,
"isWorst": false
},
{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"position": 2,
"isWorst": false
},
{
"ts": ISODate("2023-09-27T00:00:00.000Z"),
"value": 100,
"position": 2,
"isWorst": false
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"position": 3,
"isWorst": false
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"position": 4,
"isWorst": true
}]
Fazit
Die obigen Beispiele zeigen, dass das Ranking von Werten in einer Gruppe von Dokumenten mit $setWindowFields
äußerst einfach ist. Man kann die obigen Stages selbstverständlich noch mit passenden Partitions-Einstellungen versehen, um die Ranking jeweils innerhalb einer Gruppe von Dokumenten vorzunehmen.
Geschäftsführer
- Einen gleitenden Durchschnitt mit MongoDB berechnen
- Lücken füllen mit $densify und $fill
- Materialized Views mit $out und $merge
- Deltas berechnen mit $setWindowFields
- Gesamtwerte und prozentuale Anteile ermitteln
- Wer hat gewonnen? Ranking in der Aggregation Pipeline