MongoDB

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.


MongoDB Analyse Time Series Collections NoSql
Markus Wildgruber
Markus Wildgruber

Geschäftsführer

  • CloudArchitect
  • DeveloperCoach
  • DotNet
  • MongoDB
  • Angular