MongoDB

MongoDB ist die führende Open-Source, Document Datenbank die für einfache Entwicklung und Skalierung aber auch für Big Data Szenarien entwickelt wurde.

Neue Features in 4.2 - Pipeline Powered Updates und mehr Möglichkeiten für Abfragen

Marc-David Militz
Experte
  • Artikel von Dj Walker-Morgan

    • englischer Originalartikel
      https://www.mongodb.com/blog/post/coming-in-mongodb-42-pipeline-powered-updates-and-more-expressive-queries
      Übersetzung mit freundlicher Genehmigung von MongoDB


      Mit einigen Features der kommenden Version habe ich mich ja bereits beschäftigt.
      https://www.qualiero.com/community/mongodb/neuigkeiten/neue-features-in-4-2-kompression-rotierende-schluessel-und-konfiguration.html
      Heute wollen wir dafür mal einen Blick auf die Verbesserungen bei Updates und der Aggregation Pipeline werfen.

      Wenn wir in MongoDB etwas anderes als reine CRUD Operationen machen wollen, dann landen wir ganz schnell beim Aggregation Framework.
      Damit können wir eine sehr mächtige Pipeline für Operationen erstellen, um Dokumente zu aggregieren oder zu transformieren.
      In MongoDB 4.2 gibt es nun einige Erweiterungen des "update" Befehls, der die Möglichkeiten dieser Operation deutlich erweitert.
      Und wie eigentlich bei jeder neuen Version, so gibt es auch diesmal neue Operatoren und Ausdrücke für die Aggregation, wie z.B. Trigonometrie, Reguläre Ausdrücke und aktuelle Zeit.

      • Überall Pipelines

        • In MongoDB 4.2 gibt es eine bedeutende Veränderung wofür man Aggregation Pipelines einsetzen kann.
          Bisher waren Pipelines auf den "aggregate" Befehl beschränkt.
          In der neuen Version können Pipelines auch als Teil der "update" und "findAndModify" Befehle genutzt werden.
          https://docs.mongodb.com/master/reference/method/db.collection.update/index.html
          https://docs.mongodb.com/master/reference/method/db.collection.findAndModify/
          Hier ein paar Beispiele, wie man das einsetzt.
          Als erstes benötigen wir ein Dokument:
          > db.exp.insertOne({ _id:"x", val1: 100, val2: 200 });
          { "acknowledged" : true, "insertedId" : "x" }

          Bisher mussten wir, wenn wir "val1" und "val2" addieren wollten, die beiden Felder addieren und das Dokument updaten, da man im "update" die Felder nicht referenzieren konnte.
          > db.exp.update({ _id:"x" }, { $set: { total: 300 } })
          WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
          > db.exp.findOne()
          { "_id" : "x", "val1" : 100, "val2" : 200, "total" : 300 }

          Das beinhaltet natürlich einen Roundtrip und in der realen Welt, außerhalb dieser Beispiele, wäre es wohl Teil anderer Updates.
          Clevere MongoDB Nutzer hätten stattdessen auch die Aggregation Pipeline verwendet, da diese einen "$sum" Operator besitzt, den man dafür nutzen kann.
          > db.exp.aggregate( [ { $set: { total: { $sum:[ "$val1","$val2" ] } } } ])
          { "_id" : "x", "val1" : 100, "val2" : 200, "total" : 300 }

          Das Ergebnis wird allerdings nicht persistiert.
          > db.exp.findOne()
          { "_id" : "x", "val1" : 100, "val2" : 200 }

          So wie dieses Minimalbeispiel gebaut wurde würde es auch auf alle Dokumente einer Collection abziehlen.
          Wir müssten natürlich einen "$match" Abschnitt einfügen, um den Scope der Anweisung nur auf unser eines Dokument zu setzen.

          In MongoDB 4.2 können wir nun die Möglichkeiten der Aggregation Pipeline auch im "update" Befehl nutzen:
          > db.exp.update({"_id" : "x" }, [ { $set: { total: { $sum:[  "$val1","$val2" ] } } } ] )
          WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
          > db.exp.findOne();
          { "_id" : "x", "val1" : 100, "val2" : 200, "total" : 300 }

          Wir verschieben einfach die Aggregation Pipeline in unser Update und die Änderungen werden an dem Dokument durchgeführt.
          Der Feldwert, den wir setzen, wird in dem Dokument gespeichert.
          Und das Beste dabei ist, dass dies alles ohne zusätzliche Server Roundtrips geschieht.
          Das beinhaltet auch die Fähigkeit des Aggregation Frameworks, bedingte Anweisungen wie diese, auszuführen:
          > db.exp.update({ _id:"x" }, [
          {
          "$set": {
          "status": {
          "$cond": {
          "if": { "$gt": ["$total", 200] },
          "then": "Over Limit",
          "else": "Clear"
          }
          }
          }
          }
          ])

          Diese neue Funktionalität ist Teil der Bemühungen der MongoDB Entwickler, die Abfragesprache in den Queries und Aggregationen zu vereinheitlichen und überall die gleichen Möglichkeiten zu bieten.
          Wann immer wir künftig also über Verbesserungen im Aggregation Framework sprechen, dann gelten diese ebenso für den "update" und den "findAndModify" Befehl, sofern diese die Aggregation Pipeline nutzen.

          Wer sich mit dem Aggregation Framework bereits auskennt, der hat sich vielleicht gewundert wo der "$set" Schritt herkam.
          Dieser ist ebenso ein neues Feature in MongoDB 4.2, allerdings nicht ganz so neu.
          Es handelt sich um ein Alias für "$addFields" und wurde eingeführt, um Vereinheitlichung der Abfragesprache nahtloser zu gestalten.
          Insgesamt gibt es drei aggregationsstufen, die im "update" genutzt werden können, jede davon hat ihren bisherigen Namen sowie einen Aliasnamen.
          "$set"/"$addFields", "$unset"/"$project", und "$replaceWith"/"$replaceRoot".
          Diese drei Stufen ermöglichen es neue Felder hinzuzufügen, Felder zu entfernen und das komplette Dokument zu ersetzen.
          Das ermöglicht alle Aktionen, die man auf ein Dokument anwenden können wollte.

          • Reibungslose mathematische Operatoren

            • Die Möglichkeit mit herkömmlichen trigonomischen Funktionen zu rechnen, ist etwas, dass man erst dann vermisst, wenn es nicht da ist.
              Mit MongoDB 4.2 wird das Risiko dieses "Vermissens" deutlich verringert, da eine Reihe von trigonomischen Ausdrücken zur Aggregation Pipeline hinzugefügt wurde.
              Sinus, Cosinus, Tanges sowie deren inverse und inverse hyperbolische Varianten werden durch die Befehle "$sin", "$cos", "$tan", "$asin", "$acos", "$atan", "$atan2", "$asinh", "$acosh" und "$atanh" unterstützt.
              Die Details dazu finden sich bei den Operatoren der Aggregation Pipeline
              https://docs.mongodb.com/master/reference/operator/aggregation/
              All diese Ausdrücke funktionieren auch mit dem Bogenmaß (radians) deshalb gibt es auch die "$degreesToRadians" und "$radiansToDegrees" Ausdrücke.

              Des Weiteren gibt es einen neuen "$round" Ausdruck, der numerische Werte auf eine spezifische Anzahl von Zahlen oder Nachkommastellen rundet.
              Zu beachten ist, dass der ältere "$trunc" Ausdruck, der bisher Werte auf einen Integer gerundet hat, erweitert wurde, um numerische Werte auf eine spezifische Anzahl von Zahlen oder Nachkommastellen zu runden.
              Allerdings behält er sein "altes" Verhalten, wenn er mit der "alten" Syntax aufgerufen wird.

              Fügen wir all das in eine Abfrage auf unser Beispieldokument zusammen:
              >db.exp.update({"_id" : "x" }, [ { $set: { sin: { $round: [ { $sin:"$val1" } , 5 ] } } } ] )
              WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
              > db.exp.findOne();
              {
              "_id" : "x",
              "val1" : 100,
              "val2" : 200,
              "total" : 300,
              "sin" : -0.50637
              }

              Wir holen uns den Sinuswert unseres "val1" Feldes und runden diesen auf 5 Nachkommastellen, danach speichern wir das Ergebnis wieder im Dokument in das Feld "sin".

              • Reguläre Ausdrücke für alle

                • Bis MongoDB 4.2 war es lediglich in der "$match" Stufe möglich den "$regex" Operator, in Aggregationen, zu verwenden.
                  Das bedeutet, man konnte ihn nur für das Matching, aber nicht zum parsen und extrahieren eines Teilstrings nutzen.
                  Dies verändert sich nun mit den drei neuen Operatoren "$regexFind", "$regexFindAll" und "$regexMatch".
                  https://docs.mongodb.com/master/reference/operator/aggregation/regexFind/
                  Schauen wir uns da mal an und fügen ein paar Dokumente mit Strings ein:
                  > db.reg.insertOne( { text:"Looking for 100 numbers" } )
                  > db.reg.insertOne( { text:"Digging around in 256 digits" } )
                  > db.reg.insertOne( { text:"Filtering through 65 characters" } )

                  Suchen wir nun nach der Zahl, die in den Texten vorkommt, allerdings nur dann, wenn es von einer Referenz zu "numbers" oder "digits" begleitet wird.
                  Wir benutzen dafür den Ausdruck "([0-9]+) (numbers|digits)".
                  Das erfasst sowohl die Zahl auch das darauffolgende Wort, sofern es in den Klammern angegeben wurde.
                  Führen wir die Abfrage aus und sehen uns das Ergebnis an:
                  > db.reg.aggregate( { $set: { found: { $regexFind: { regex: "([0-9]+) (numbers|digits)", input:"$text" } } } } )
                  { "_id" : ObjectId("5d3f059c7baafd6bbd862d47"), "text" : "Looking for 100 numbers", "found" : { "match" : "100 numbers", "idx" : 12, "captures" : [ "100", "numbers" ] } }
                  { "_id" : ObjectId("5d3f059c7baafd6bbd862d48"), "text" : "Digging around in 256 digits", "found" : { "match" : "256 digits", "idx" : 18, "captures" : [ "256", "digits" ] } }
                  { "_id" : ObjectId("5d3f076f7baafd6bbd862d49"), "text" : "Filtering through 65 characters", "found" : null }

                  Wenn wir uns das Feld mit den Ergebnissen des regulären Ausdrucks ansehen, dann finden wir mehr als ein einfaches true/false:
                  "found" : { "match" : "100 numbers", "idx" : 12, "captures" : [ "100", "numbers" ] }

                  In dem zurückgegebenen Feld "match" bekommen wir den exakten String, der auf unser Suchmuster passt.
                  Das "idx" Feld zeigt an wie weit im durchsuchten String das Ergebnis gefunden wurde.
                  Das Feld "captures" enthält schließlich die einzelnen erfassten Teile des übereinstimmenden Strings, als erstes Element die gefundene Zahl und als zweites das Wort "digit" oder "number".
                  Das ist ideal für komplexes Parsen von Strings.
                  Wenn es kein Ergebnis gibt, dann gibt "$regexFind" einfach ein "NULL" zurück.

                  Mit "$regexFind" erhält man lediglich den ersten Treffer zurück.
                  Wenn man mehrere Treffer zurückbekommen möchte, dann werden mit dem "$regexFindAll" alle übereinstimmenden Treffer ermittelt und das Ergebnis entsprechend als Array zurückgegeben.
                  Gibt es keine Treffer wird ein leeres Array zurückgegeben.

                  Wenn man tatsächlich lediglich ein "true" oder "false" als Antwort bekommen möchte, dann kann man dafür "$regexMatch" verwenden.

                  • Endlich: $$NOW

                    • Einer der häufigsten Wünsche der Nutzer, nämlich einen Timestamp in ein Dokument einzufügen, ohne dabei einen Server-Roundtrip machen zu müssen, wurde endlich erhört.
                      In MongoDB 4.2 können wir nun "$$NOW" als Variable in Aggregation Pipelines verwenden.
                      Diese gibt den aktuellen Timestamp als ISODate zurück.

                      Wer Interesse hat, den aktuellen Release Candidate von MongoDB 4.2, mal auszuprobieren, dem seine die aktuellen Release Notes empfohlen
                      https://docs.mongodb.com/master/release-notes/4.2/
                      Der Development Build kann über das Download Center heruntergeladen werden
                      https://www.mongodb.com/download-center/community

Neueste Mitgliederaktivitäten

Tags

Diesen Community Beitrag weiterempfehlen