sql >> Database >  >> NoSQL >> MongoDB

Veld verwijderen gevonden in een mongodb-array

Dus ik heb een vraag gesteld in de reacties, maar het lijkt erop dat je weggelopen bent, dus ik denk dat ik gewoon antwoord op de drie mogelijke gevallen die ik zie.

Om te beginnen weet ik niet zeker of de elementen die in de geneste arrays worden weergegeven, de enige zijn elementen binnen de array of in feite als arrayToDelete is de enige veld aanwezig in die elementen. Dus eigenlijk moet ik abstreren een beetje en voeg dat geval toe:

{
    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"
            },
        ],
    ]
}

Geval 1 - Verwijder de binnenste array-elementen waar het veld aanwezig is

Dit zou de $pull . gebruiken operator, want dat is wat matrixelementen verwijdert geheel. Je doet dit in moderne MongoDB met een statement als dit:

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

Dat verandert alle overeenkomende documenten als volgt:

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

Dus elk element dat dat veld bevatte is nu verwijderd.

Geval 2 - Verwijder gewoon het overeenkomende veld van de binnenste elementen

Hier gebruik je $unset . Het is net iets anders dan de "hard geïndexeerd" versie die je aan het doen was:

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

Wat alle overeenkomende documenten verandert in:

{
        "_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"
                        }
                ]
        ]
}

Dus alles is er nog, maar alleen de geïdentificeerde velden zijn verwijderd uit elk inner array-document.

Geval 3 - U wilde eigenlijk "Alles" in de array verwijderen.

Wat eigenlijk gewoon een simpel geval is van het gebruik van $set en alles wissen wat er eerder was:

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

Waar de resultaten vrij goed te verwachten zijn:

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

Dus wat doen deze allemaal?

Het allereerste dat u zou moeten zien, is het querypredikaat . Dit is over het algemeen een goed idee om er zeker van te zijn dat u niet overeenkomt en zelfs niet 'probeert' om updatevoorwaarden te hebben voor documenten die niet eens gegevens bevatten met het patroon dat u wilt bijwerken. Geneste arrays zijn moeilijk op zijn best, en waar praktisch gezien moet je ze echt vermijden, zoals je vaak "echt meent" wordt eigenlijk weergegeven in een enkelvoudige array met extra attributen die vertegenwoordigen wat u "denkt" de nesting echt voor je doet.

Maar alleen omdat ze moeilijk zijn betekent niet onmogelijk . U moet alleen $elemMatch . begrijpen :

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

Dat is de basis find() voorbeeld, dat overeenkomt op basis van de $elemMatch voorwaarde voor de buitenste array gebruikt een andere $elemMatch om te voldoen aan een andere voorwaarde in de innerlijke reeks. Hoewel dit 'verschijnt' een enkelvoudig predikaat zijn. Iets als:

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

Gaat gewoon niet werken. Ook niet:

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

Met de "dubbele punt" .. want dat is eigenlijk gewoon niet geldig.

Dat is het querypredikaat om te matchen met "documenten" die moeten worden verwerkt, maar de rest is van toepassing om daadwerkelijk te bepalen *welke delen moeten worden bijgewerkt".

In het Geval 1 om $pull van de innerlijke array, moeten we eerst kunnen identificeren welke elementen van de outer array bevatten de gegevens die moeten worden bijgewerkt. Dat is wat de "scan.$[a]" ding doet het gebruik van de positioneel gefilterde $[<identifier>] telefoniste.

Die operator transponeert in feite de overeenkomende indexen ( zo veel of them ) in de array naar een ander predikaat die is gedefinieerd in het derde deel van de update stijl commando's met de arrayFilters sectie. Deze sectie definieert in principe de voorwaarden waaraan moet worden voldaan vanuit het perspectief van de genoemde identifier.

In dit geval heet onze "identifier" a , en dat is het voorvoegsel dat wordt gebruikt in de arrayFilters invoer:

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

Genomen in context met de feitelijke update-instructie deel:

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

Dan vanuit het perspectief van de "a" zijnde de identifier voor de buitenste array-element eerst naar binnen van "scan" , dan gelden dezelfde voorwaarden als voor het originele querypredikaat maar van "binnen" de eerste $elemMatch uitspraak. Je kunt dit dus eigenlijk zien als een "query binnen een query" vanuit het perspectief van al "binnenkijken" de inhoud van elke buitenste element.

Op dezelfde manier de $pull werkt ongeveer als een "query binnen een query" in die zin dat zijn eigen argumenten ook worden toegepast vanuit het perspectief van het element van de array. Daarom alleen de arrayToDelete veld bestaat in plaats van:

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

Maar dat is allemaal specifiek voor $pull , en andere dingen hebben verschillende gevallen:

De Case 2 kijkt naar waar je gewoon wilt $unset het genoemde veld. Het lijkt vrij eenvoudig als je het veld een naam geeft, toch? Nou niet precies, want het volgende klopt duidelijk niet van wat we eerder weten:

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

En natuurlijk is het gewoon lastig om array-indexen voor alles op te merken:

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

Dit is de reden voor de positionele all $[] exploitant. Deze is iets meer "brute force" dan de positioneel gefilterde $[<identifier>] daarin in plaats van een ander predikaat te matchen geleverd binnen arrayFilters , wat dit gewoon doet, is van toepassing op alles binnen de array-inhoud op die "index". Het is eigenlijk een manier om "alle indexen" . te zeggen zonder ze allemaal uit te typen, zoals de verschrikkelijke hierboven getoond geval.

Het is dus niet voor alle gevallen , maar het is zeker goed geschikt voor een $unset want dat heeft een zeer specifieke padnaam wat natuurlijk niet uitmaakt als dat pad niet overeenkomt met elk afzonderlijk element van de array.

Je zou gebruik nog steeds een arrayFilters en een positioneel gefilterde $[<identifier>] , maar hier zou het overdreven zijn. Bovendien kan het geen kwaad om de andere aanpak te demonstreren.

Maar natuurlijk is het waarschijnlijk de moeite waard om te begrijpen hoe precies die verklaring eruit zou zien, dus:

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

Merk op dat de "b.arrayToDelete" is misschien niet wat je in eerste instantie verwacht, maar gezien de positionering in "scan.$[a].$[b] het zou echt logisch moeten zijn vanaf de b de elementnaam zou worden bereikt via "puntnotatie", net zoals weergegeven. En eigenlijk in beide gevallen. Nogmaals, een $unset zou sowieso alleen van toepassing zijn op het genoemde veld, dus de selectiecriteria zijn echt niet vereist.

En Geval 3 . Nou, het is vrij eenvoudig in die zin dat als je niet nodig om iets anders in de array te behouden na het verwijderen van deze inhoud ( bijv. een $pull waar velden die hiermee overeenkomen de enige . waren dingen erin, of een $unset wat dat betreft), rommel dan gewoon niet met iets anders en wis de array gewoon af .

Dit is een belangrijk onderscheid als u bedenkt dat vanaf het punt om te verduidelijken of de documenten met het genoemde veld de alleen elementen binnen de geneste arrays, en inderdaad dat de benoemde sleutel de enige . was ding aanwezig in de documenten.

Met als redenering dat het gebruik van $pull zoals hier getoond en onder die voorwaarden zou je krijgen:

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

Of met de $unset :

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

Beide zijn duidelijk niet wenselijk. Het is dus logisch als de arrayToDelete veld was de enige inhoud die er überhaupt in zat, dan is de meest logische manier om alles te verwijderen is gewoon om de array te vervangen door een lege. Of inderdaad $unset de hele documenteigenschap.

Merk echter op dat al deze "fancy dingen" ( met uitzondering van de $set natuurlijk ) vereisen dat u op zijn minst MongoDB 3.6 moet hebben beschikbaar om deze functionaliteit te gebruiken.

In het geval dat u nog steeds een oudere versie van MongoDB gebruikt dan dat (en vanaf de datum van schrijven zou u dat echt niet moeten zijn, aangezien uw officiële ondersteuning binnen slechts 5 maanden vanaf deze datum afloopt) dan andere bestaande antwoorden over het bijwerken van meerdere Array-elementen in mongodb zijn eigenlijk voor jou.



  1. Verbeter de MongoDB-aggregatiestructuur

  2. Hadoop-ecosysteem - Inleiding tot Hadoop-componenten

  3. Spring Redis-fouthandvat

  4. Redis als berichtenmakelaar