Deltas berechnen mit $setWindowFields
Eine der mächtigsten Aggregation Stages ist $setWindowFields. Sie ermöglicht das Ausführen von Berechnungen innerhalb eines Fensters und legt das Ergebnis jeweils in einem Feld auf dem Dokument ab. Hierbei kann auch auf das Vorgänger-Dokument zurückgegriffen werden, so dass Vergleiche möglich werden.
Im ersten Schritt gehen wir von folgender einfachen Dokumentenstruktur aus, die jeweils einen Zeitstempel und einen Wert umfasst:
[{
"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
}]
Die folgende einfache Aggregation-Pipeline kann zum Ermitteln des Deltas genutzt werden:
[
{
$setWindowFields: {
partitionBy: null,
sortBy: { ts: 1 },
output: {
prev: {
$first: "$value",
window: {
documents: [-1, -1],
},
},
},
},
},
{
$set: {
delta: {
$subtract: ["$value", "$prev"],
},
},
},
]
Die erste Stage dient dazu, den jeweiligen Vorgänger-Wert zu erhalten; hierbei kommt $setWindowFields zum Einsatz, wobei vorerst auf eine Partitionierung verzichtet wird und die Dokumente nach dem Zeitstempel sortiert werden. Im output wird über die Angabe von documents: [-1, -1]
angegeben, dass das jeweilige Vorgänger-Dokument genutzt werden soll. Die $first-Funktion nimmt dann den ersten Wert aus dem Array der Dokumente und speichert diesen im Feld prev
ab.
In der folgenden $set-Stage wird anschließend die Differenz zwischen dem Vorgänger-Wert und dem aktuellen Wert gebildet, so dass folgende Daten ausgegeben werden:
[{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"prev": null,
"delta": null
},
{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"prev": 100,
"delta": 50
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"prev": 150,
"delta": -100
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"prev": 50,
"delta": -40
}]
Partitionierung
Normalerweise ist die Dokumentenstruktur nicht so simpel wie im ersten Beispiel; zumindest werden in der Regel Daten mehrerer Sensoren in einer Collection gespeichert:
[{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"sensor": 1
},
{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"sensor": 2
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"sensor": 1
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"sensor": 2
}]
Wir können die Daten also nach Sensor-Id partitionieren, so dass jeweils der richtige Vorgänger verwendet wird:
[{
"ts": ISODate("2023-09-20T00:00:00.000Z"),
"value": 100,
"sensor": 1,
"prev": null,
"delta": null
},
{
"ts": ISODate("2023-09-24T00:00:00.000Z"),
"value": 50,
"sensor": 1,
"prev": 150,
"delta": -50
},
{
"ts": ISODate("2023-09-21T00:00:00.000Z"),
"value": 150,
"sensor": 2,
"prev": null,
"delta": null
},
{
"ts": ISODate("2023-09-26T00:00:00.000Z"),
"value": 10,
"prev": 150,
"delta": -140
}]
Fazit
Die obigen einfachen Beispiele zeigen, wie durch die Verwendung von $setWindowFields der Unterschied zu vorhergehenden Werten ermittelt werden kann. Es lohnt sich sehr, einen genaueren Blick auf die Aggregation Stage $setWindowFields zu werden, und die vielfältigen Möglichkeiten zu entdecken.
Geschäftsführer