sql >> Databasteknik >  >> NoSQL >> MongoDB

Ta bort fält som finns i valfri mongodb-array

Så jag ställde en fråga i kommentarerna men du verkar ha gått iväg, så jag antar att jag bara svarar på de tre möjliga fall jag ser.

Till att börja med är jag inte säker på om elementen som visas i de kapslade arrayerna är de enda element inom arrayen eller faktiskt om arrayToDelete är den enda fält som finns i dessa element. Så i princip behöver jag abstrahera lite och inkludera det fallet:

{
    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 - Ta bort de inre arrayelementen där fältet finns

Detta skulle använda $pull operatorn eftersom det är det som tar bort arrayelement helt. Du gör detta i moderna MongoDB med ett uttalande så här:

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

Det ändrar alla matchande dokument så här:

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

Så varje element som innehöll det fältet är nu borttaget.

Fall 2 - Ta bara bort det matchade fältet från de inre elementen

Det är här du använder $unset . Det är bara lite annorlunda än den "hårt indexerade" version du gjorde:

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

Vilket ändrar alla matchade dokument till att vara:

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

Så allt finns kvar, men bara de identifierade fälten har tagits bort från varje inre array-dokument.

Fall 3 - Du ville faktiskt ta bort "Allt" i arrayen.

Vilket egentligen bara är ett enkelt fall av att använda $set och torka av allt som fanns där innan:

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

Där resultaten ganska väl kan förväntas:

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

Så vad gör alla dessa?

Det allra första du bör se är frågepredikatet . Detta är generellt sett en bra idé för att se till att du inte matchar och till och med "försöker" att få uppdateringsvillkor uppfyllda på dokument som inte ens innehåller data med det mönster du avser att uppdatera. Kapslade arrayer är svåra i bästa fall, och där det är praktiskt möjligt bör du verkligen undvika dem, som vad du ofta "verkligen menar" är faktiskt representerad i en singular array med ytterligare attribut som representerar vad du "tror" kapslingen faktiskt gör för dig.

Men bara för att de är hårda betyder inte omöjligt . Det är bara det att du behöver förstå $elemMatch :

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

Det är den grundläggande find() exempel, som matchar baserat på $elemMatch villkor för det yttre array använder en annan $elemMatch för att matcha ett annat villkor i inre array. Även om detta "visas" att vara ett singular predikat. Något i stil med:

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

Kommer bara inte att fungera. Inte heller:

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

Med "dubbelpunkten" .. eftersom det i princip inte är giltigt.

Det är frågepredikatet att matcha "dokument" som behöver bearbetas, men resten gäller för att faktiskt bestämma *vilka delar som ska uppdateras".

I Fall 1 för att $pull från det inre array måste vi först kunna identifiera vilka element i yttre arrayen innehåller data som ska uppdateras. Det är vad "scan.$[a]" sak att göra med den positionsfiltrerade $[<identifier>] operatör.

Operatören transponerar i princip de matchade indexen ( så många av dem ) i arrayen till ett annat predikat som definieras i den tredje delen av update stilkommandon med arrayFilters sektion. Det här avsnittet definierar i grunden villkoren som ska uppfyllas utifrån den namngivna identifieraren.

I detta fall heter ut "identifierare" a , och det är prefixet som används i arrayFilters post:

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

Taget i sammanhang med själva uppdateringsutlåtandet del:

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

Sedan ur perspektivet "a" är identifieraren för yttre arrayelement först inåt från "scan" , då gäller samma villkor som för det ursprungliga frågepredikatet men från "inom" den första $elemMatch påstående. Så du kan i princip tänka på detta som en "fråga i en fråga" utifrån perspektivet att du redan "titta inuti" innehållet i varje yttre element.

På samma sätt används $pull fungerar ungefär som en "fråga i en fråga" genom att dess egna argument också tillämpas ur perspektivet av elementet i arrayen. Därför bara arrayToDelete befintligt fält istället för:

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

Men det är allt specifikt för $pull , och andra saker har olika fall:

Fall 2 tittar på var du bara vill $unset det namngivna fältet. Verkar ganska lätt eftersom du bara namnger fältet, eller hur? Tja inte precis eftersom följande uppenbarligen inte stämmer av vad vi vet tidigare:

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

Och naturligtvis är det bara jobbigt att notera arrayindex för allt:

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

Detta är anledningen till den positionella alla $[] operatör. Den här är lite mer "brute force" än den positionsfiltrerade $[<identifier>] i det istället för att matcha ett annat predikat tillhandahålls inom arrayFilters , vad detta helt enkelt gör är att gälla allt inom arrayinnehållet vid det "index". Det är i grunden ett sätt att säga "alla index" utan att skriva varenda en som den hemska fall som visas ovan.

Så det är inte för alla fall , men det är säkert väl lämpat för en $unset eftersom det har ett mycket specifikt sökvägsnamn vilket inte spelar någon roll om den vägen inte matchar varje enskilt element i arrayen.

Du kunde använder fortfarande en arrayFilters och en positionsfiltrerad $[<identifier>] , men här skulle det vara överdrivet. Dessutom skadar det inte att visa det andra tillvägagångssättet.

Men naturligtvis är det förmodligen värt att förstå hur exakt det uttalandet skulle se ut, så:

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

Notera där att "b.arrayToDelete" kanske inte är vad du förväntar dig först, men med tanke på placeringen i "scan.$[a].$[b] det borde verkligen vara vettigt från b elementnamnet skulle nås via "dot notation" precis som visas. Och faktiskt i båda fallen. Återigen, en $unset skulle bara gälla för det namngivna fältet ändå, så urvalskriterierna är verkligen inte nödvändiga.

Och Fall 3 . Tja, det är ganska enkelt om du inte behöver för att behålla något annat i arrayen efter att ha tagit bort detta innehåll (dvs. en $pull där fält som matchar detta var de enda saker där inne, eller en $unset för den delen ), så bråka helt enkelt inte med något annat utan torka av arrayen .

Detta är en viktig distinktion om du anser att enligt punkten för att klargöra om dokumenten med det namngivna fältet är endast element inom de kapslade arrayerna, och faktiskt att den namngivna nyckeln var den enda sak som finns i dokumenten.

Med resonemanget är att använda $pull som visas här och under dessa förhållanden skulle du få:

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

Eller med $unset :

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

Båda är helt klart inte önskvärda. Så det finns anledning att arrayToDelete fältet var det enda innehåll som överhuvudtaget fanns där, då det mest logiska sättet att ta bort allt är helt enkelt att ersätta arrayen med en tom. Eller faktiskt $unset hela dokumentegenskapen.

Observera dock att alla dessa "fantastiska saker" (med undantag för $set naturligtvis ) kräver att du måste ha minst MongoDB 3.6 tillgänglig för att använda den här funktionen.

Om du fortfarande kör en äldre version av MongoDB än så (och från och med skrivdatumet borde du verkligen inte vara det eftersom ditt officiella stöd tar slut på bara 5 månader från detta datum) så kommer andra befintliga svar på Hur man uppdaterar flera Array-element i mongodb är faktiskt för dig.



  1. Kan jag göra två kolumner unika för varandra? eller använda sammansatta primärnycklar i redis?

  2. Express Node.JS - Tar emot Redis-återuppringning, utför löften

  3. Skapa ett ISO-datumobjekt i javascript

  4. Hur man filtrerar array i underdokument med MongoDB