MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Entfernen Sie das Feld, das in einem beliebigen Mongodb-Array gefunden wurde

Also habe ich eine Frage in den Kommentaren gestellt, aber du scheinst weggegangen zu sein, also beantworte ich wohl nur die drei möglichen Fälle, die ich sehe.

Zunächst einmal bin ich mir nicht sicher, ob die in den verschachtelten Arrays angezeigten Elemente die nur sind Elemente innerhalb des Arrays oder tatsächlich wenn arrayToDelete ist die nur Feld in diesen Elementen vorhanden. Also im Grunde muss ich abstrahieren ein wenig und schließen Sie diesen Fall ein:

{
    field: 'value',
    field2: 'value',
    scan: [
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
        [
            {   somethingToKeep: 1 },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
            {
                arrayToDelete: [0,1,2],
                anotherField: "a"
            },
        ],
    ]
}

Fall 1 - Entfernen Sie die inneren Array-Elemente, wo das Feld vorhanden ist

Dies würde den $pull verwenden -Operator, da dieser Array-Elemente entfernt komplett. Sie tun dies in der modernen MongoDB mit einer Anweisung wie dieser:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },
  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }
)

Das ändert alle übereinstimmenden Dokumente wie folgt:

{
        "_id" : ObjectId("5ca1c36d9e31550a618011e2"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        }
                ]
        ]
}

Also wird jetzt jedes Element entfernt, das dieses Feld enthielt.

Fall 2 - Entfernen Sie einfach das übereinstimmende Feld aus den inneren Elementen

Hier verwenden Sie $unset . Es unterscheidet sich nur ein wenig von der "fest indizierten" Version, die Sie gemacht haben:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[].$[].arrayToDelete": ""  } }
)

Dadurch werden alle übereinstimmenden Dokumente wie folgt geändert:

{
        "_id" : ObjectId("5ca1c4c49e31550a618011e3"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        }
                ],
                [
                        {
                                "somethingToKeep" : 1
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        },
                        {
                                "anotherField" : "a"
                        }
                ]
        ]
}

Also ist alles noch da, aber nur die identifizierten Felder wurden aus jedem inneren Array-Dokument entfernt.

Fall 3 - Sie wollten eigentlich "Alles" aus dem Array entfernen.

Was wirklich nur ein einfacher Fall der Verwendung von $set ist und lösche alles, was vorher da war:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$set": { "scan": []  } }
)

Wo die Ergebnisse ziemlich gut erwartet werden sollten:

{
        "_id" : ObjectId("5ca1c5c59e31550a618011e4"),
        "field" : "value",
        "field2" : "value",
        "scan" : [ ]
}

Und was machen die alle?

Das allererste, was Sie sehen sollten, ist das Abfrageprädikat . Dies ist im Allgemeinen eine gute Idee, um sicherzustellen, dass Sie nicht übereinstimmen und es sogar versuchen um Aktualisierungsbedingungen für Dokumente erfüllt zu haben, die nicht einmal Daten mit dem Muster enthalten, das Sie aktualisieren möchten. Verschachtelte Arrays sind schwierig am besten, und wo praktisch, sollten Sie sie wirklich vermeiden, da Sie oft "wirklich meinen" wird tatsächlich in einem einzelnen Array mit zusätzlichen Attributen dargestellt, die darstellen, was Sie "denken" die Verschachtelung tatsächlich für Sie tut.

Aber eben weil sie hart sind bedeutet nicht unmöglich . Sie müssen lediglich $elemMatch verstehen :

db.colelction.find(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  }}
)

Das ist das grundlegende find() Beispiel, das auf der Grundlage von $elemMatch übereinstimmt Bedingung für das Äußere array verwendet ein anderes $elemMatch um eine andere Bedingung im Inneren abzugleichen Reihe. Auch wenn dies "erscheint" ein singuläres Prädikat sein. Etwas wie:

"scan.arrayToDelete": { "$exists": true }

Wird einfach nicht funktionieren. Weder würde:

"scan..arrayToDelete": { "$exists": true }

Mit dem "Doppelpunkt" .. denn das ist im Grunde einfach nicht gültig.

Das ist das Abfrageprädikat um "Dokumente" abzugleichen, die verarbeitet werden müssen, aber der Rest gilt, um tatsächlich zu bestimmen, *welche Teile aktualisiert werden sollen".

Im Fall 1 um $pull zu machen von innerem -Array müssen wir zuerst in der Lage sein zu identifizieren, welche Elemente des äußeren Array enthält die zu aktualisierenden Daten. Dafür steht der "scan.$[a]" Ding verwendet den positionsgefilterten $[<identifier>] Betreiber.

Dieser Operator transponiert die übereinstimmenden Indizes ( so viele of them ) im Array zu einem anderen Prädikat die im dritten Abschnitt des update definiert ist Style-Befehle mit den arrayFilters Sektion. Dieser Abschnitt definiert im Wesentlichen die zu erfüllenden Bedingungen aus Sicht des benannten Bezeichners.

In diesem Fall heißt unser "Bezeichner" a , und das ist das Präfix, das in den arrayFilters verwendet wird Eintrag:

  { "arrayFilters": [
      {  "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
    ]
  }

Im Zusammenhang mit der eigentlichen Update-Anweisung genommen Teil:

  {
    "$pull": {
      "scan.$[a]": { "arrayToDelete": { "$exists": true } }
    }
  },

Dann aus der Perspektive des "a" der Bezeichner für das Äußere ist Array-Element zuerst nach innen von "scan" , dann gelten dieselben Bedingungen wie für das ursprüngliche Abfrageprädikat sondern von "innerhalb" die erste $elemMatch Erklärung. Sie können sich dies also im Grunde als eine "Abfrage innerhalb einer Abfrage" aus der Perspektive eines bereits "hineinschauenden" vorstellen der Inhalt von jedem Äußeren Element.

Ebenso der $pull verhält sich ähnlich wie eine "Abfrage innerhalb einer Abfrage" , dass seine eigenen Argumente auch aus der Perspektive des Elements des Arrays angewendet werden. Daher nur das arrayToDelete Feld vorhanden statt:

  // This would be wrong! and do nothing :(
  {
    "$pull": {
      "scan.$[a]": { "$elemMatch": { "arrayToDelete": { "$exists": true } } }
    }
  }

Aber das ist alles spezifisch für $pull , und andere Dinge haben unterschiedliche Fälle:

Der Fall 2 schaut, wo Sie nur $unset wollen das benannte Feld. Scheint ziemlich einfach zu sein, da Sie das Feld einfach benennen, oder? Nun, nicht genau, da das Folgende eindeutig nicht richtig ist, was wir früher wissen:

  { "$unset": { "scan.arrayToDelete": ""  } } // Not right :(

Und natürlich ist es nur mühsam, Array-Indizes für alles zu notieren:

  { "$unset": { 
    "scan.0.0.arrayToDelete": "",
    "scan.0.1.arrayToDelete": "",
    "scan.0.2.arrayToDelete": "",
    "scan.0.3.arrayToDelete": "",  // My fingers are tired :-<
  } }

Dies ist der Grund für die positionellen alle $[] Operator. Dieser hier ist etwas mehr "brute force" als der positionsgefilterte $[<identifier>] darin, anstatt ein anderes Prädikat abzugleichen innerhalb von arrayFilters bereitgestellt , es gilt einfach für alles innerhalb des Array-Inhalts an diesem "Index". Es ist im Grunde genommen eine Art, "alle Indizes" zu sagen ohne jedes einzelne wie das schreckliche abzutippen oben gezeigter Fall.

Also nicht für alle Fälle , aber es ist sicherlich gut für ein $unset geeignet da das eine sehr spezifische Pfadbenennung hat was natürlich nicht wichtig ist, wenn dieser Pfad nicht zu jedem einzelnen Element des Arrays passt.

Sie könnten immer noch einen arrayFilters verwenden und ein positionsgefilterter $[<identifier>] , aber hier wäre es übertrieben. Außerdem schadet es nicht, den anderen Ansatz zu demonstrieren.

Aber natürlich lohnt es sich zu verstehen, wie genau diese Anweisung würde so aussehen:

db.collection.updateMany(
  { "scan": {
    "$elemMatch": {
      "$elemMatch": {
        "arrayToDelete": { "$exists": true }
      }
    }
  } },
  { "$unset": { "scan.$[a].$[b].arrayToDelete": ""  } },
  {
    "arrayFilters": [
      { "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } },
      { "b.arrayToDelete": { "$exists": true } },
    ]
  }
)

Beachten Sie dort, dass der "b.arrayToDelete" mag zunächst nicht das sein, was Sie erwarten, aber angesichts der Positionierung in "scan.$[a].$[b] ab dem b sollte es wirklich Sinn machen Der Elementname würde wie gezeigt über die "Punktnotation" erreicht. Und zwar in beiden Fällen. Wieder einmal ein $unset würde ohnehin nur auf das benannte Feld zutreffen, also ist das Auswahlkriterium eigentlich nicht erforderlich.

Und Fall 3 . Nun, es ist ganz einfach, wenn Sie es nicht brauchen um alles andere im Array zu behalten, nachdem dieser Inhalt entfernt wurde (z. B. ein $pull wobei Felder, die dazu passen, die einzigen waren Dinge darin oder ein $unset für diese Angelegenheit ), dann spielen Sie einfach nicht mit irgendetwas anderem herum und löschen Sie einfach das Array .

Dies ist eine wichtige Unterscheidung, wenn man bedenkt, dass gemäß dem Punkt zu klären ist, ob die Dokumente mit dem benannten Feld nur waren Elemente innerhalb der verschachtelten Arrays, und tatsächlich, dass der benannte Schlüssel der einzige war Sache in den Dokumenten vorhanden.

Mit der Begründung, dass $pull verwendet wird wie hier gezeigt und unter diesen Bedingungen erhalten Sie:

{
        "_id" : ObjectId("5ca321909e31550a618011e6"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [ ],
            [ ],
            [ ]
        ]
}

Oder mit dem $unset :

{
        "_id" : ObjectId("5ca322bc9e31550a618011e7"),
        "field" : "value",
        "field2" : "value",
        "scan" : [
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }],
            [{ }, { }, { }, { }]
        ]
}

Beides ist eindeutig nicht wünschenswert. Es liegt also nahe, wenn das arrayToDelete Feld war das nur Inhalte, die überhaupt drin waren, dann ist es am logischsten, alle zu entfernen ist einfach, das Array durch ein leeres zu ersetzen. Oder tatsächlich $unset die gesamte Dokumenteigenschaft.

Beachten Sie jedoch, dass all diese ausgefallenen Dinger (mit Ausnahme von $set natürlich ) erfordern, dass Sie mindestens MongoDB 3.6 haben verfügbar, um diese Funktionalität zu nutzen.

Für den Fall, dass Sie immer noch eine ältere Version von MongoDB als diese ausführen (und zum Zeitpunkt des Schreibens sollten Sie dies wirklich nicht sein, da Ihr offizieller Support in nur 5 Monaten ab diesem Datum ausläuft), finden Sie andere vorhandene Antworten zum Aktualisieren mehrerer Array-Elemente in mongodb sind eigentlich für Sie.