MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Supprimer le champ trouvé dans n'importe quel tableau mongodb

J'ai donc posé une question dans les commentaires, mais vous semblez vous être éloigné, alors je suppose que je réponds simplement aux trois cas possibles que je vois.

Pour commencer, je ne suis pas certain que les éléments affichés dans les tableaux imbriqués soient uniquement éléments dans le tableau ou en fait si arrayToDelete est le seul champ présent dans ces éléments. Donc, fondamentalement, j'ai besoin de résumer un peu et incluez ce cas :

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

Cas 1 - Supprimez les éléments de tableau internes où le champ est présent

Cela utiliserait le $pull car c'est ce qui supprime les éléments du tableau entièrement. Vous faites cela dans MongoDB moderne avec une déclaration comme celle-ci :

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

Cela modifie tous les documents correspondants comme ceci :

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

Ainsi, chaque élément qui contenait ce champ est maintenant supprimé.

Cas 2 - Supprimez simplement le champ correspondant des éléments internes

C'est ici que vous utilisez $unset . C'est juste un peu différent du "hard indexed" version que vous faisiez :

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

Ce qui modifie tous les documents correspondants :

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

Donc, tout est toujours là, mais seuls les champs identifiés ont été supprimés de chaque document de tableau interne.

Cas 3 - Vous vouliez en fait supprimer "Tout" dans le tableau.

Ce qui est vraiment juste un cas simple d'utilisation de $set et en essuyant tout ce qui s'y trouvait auparavant :

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

Où les résultats devraient plutôt être attendus :

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

Alors, que font-ils tous ?

La toute première chose que vous devriez voir est le prédicat de requête . C'est généralement une bonne idée de s'assurer que vous ne correspondez pas et même "tentez" pour que les conditions de mise à jour soient remplies sur des documents qui ne contiennent même pas de données avec le modèle que vous avez l'intention de mettre à jour. Les tableaux imbriqués sont difficiles au mieux, et dans la mesure du possible, vous devriez vraiment les éviter, car ce que vous vraiment dire" est souvent est en fait représenté dans un tableau singulier avec des attributs supplémentaires représentant ce que vous "pensez" la nidification fait pour vous.

Mais juste parce qu'ils sont durs ne signifie pas impossible . C'est juste que vous devez comprendre $elemMatch :

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

C'est la base find() exemple, qui correspond en fonction de $elemMatch condition pour l'extérieur tableau utilise un autre $elemMatch afin de correspondre à une autre condition dans le intérieur déployer. Même si cela "apparaît" être un prédicat singulier. Quelque chose comme :

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

Ne fonctionnera tout simplement pas. Ni l'un ni l'autre :

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

Avec le "double point" .. parce que ce n'est tout simplement pas valide.

C'est le prédicat de requête pour correspondre aux "documents" qui doivent être traités, mais le reste s'applique pour déterminer réellement *quelles parties mettre à jour".

Dans le cas 1 afin de $pull de l'intérieur tableau, nous devons d'abord être en mesure d'identifier quels éléments du extérieur tableau contient les données à mettre à jour. C'est ce que le "scan.$[a]" chose est en train de faire en utilisant le $[<identifier>] filtré par position opérateur.

Cet opérateur transpose essentiellement les indices correspondants (tellement beaucoup d'entre eux) dans le tableau à un autre prédicat qui est défini dans la troisième section de la update commandes de style avec les arrayFilters section. Cette section définit essentiellement les conditions à remplir du point de vue de l'identifiant nommé.

Dans ce cas, notre "identifiant" est nommé a , et c'est le préfixe utilisé dans le arrayFilters entrée :

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

Pris en contexte avec la instruction de mise à jour réelle partie :

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

Puis du point de vue du "a" étant l'identifiant de l'élément externe élément de tableau premier vers l'intérieur à partir de "scan" , les mêmes conditions s'appliquent que pour le prédicat de requête d'origine mais de "dedans" le premier $elemMatch déclaration. Vous pouvez donc considérer cela comme une "requête dans une requête" du point de vue de déjà "regarder à l'intérieur" le contenu de chaque externe élément.

De même le $pull agit un peu comme une "requête dans une requête" en ce que ses propres arguments sont également appliqués du point de vue de l'élément du tableau. Donc juste le arrayToDelete champ existant au lieu de :

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

Mais tout cela est spécifique à $pull , et d'autres choses ont des cas différents :

Le cas 2 regarde où vous voulez juste $unset le champ nommé. Cela semble assez facile car vous venez de nommer le champ, n'est-ce pas ? Eh bien pas exactement puisque ce qui suit n'est clairement pas correct d'après ce que nous savons plus tôt :

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

Et bien sûr, noter les index de tableau pour tout est une corvée :

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

C'est la raison du positionnel all $[] opérateur. Celui-ci est un peu plus "force brute" que le filtre positionnel $[<identifier>] en ce qu'au lieu de correspondre à un autre prédicat fourni dans arrayFilters , cela s'applique simplement à tout dans le contenu du tableau à cet "index". C'est essentiellement une façon de dire en fait "tous les index" sans taper tout le monde comme l'horrible cas illustré ci-dessus.

Ce n'est donc pas pour tous les cas , mais il est certainement bien adapté à un $unset puisque cela a un nom de chemin très spécifique ce qui n'a pas d'importance bien sûr si ce chemin ne correspond pas à chaque élément du tableau.

Vous pourriez utilisez toujours un arrayFilters et un $[<identifier>] filtré positionnellement , mais ici ce serait exagéré. De plus, cela ne fait pas de mal de démontrer l'autre approche.

Mais bien sûr, cela vaut probablement la peine de comprendre comment exactement cette déclaration ressemblerait, donc :

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

Notant là que le "b.arrayToDelete" peut ne pas être ce à quoi vous vous attendiez au début, mais étant donné le positionnement dans "scan.$[a].$[b] cela devrait vraiment avoir un sens à partir du b le nom de l'élément serait atteint via la "notation par points" comme indiqué. Et en fait dans les deux cas. Encore une fois, un $unset ne s'appliquerait de toute façon qu'au champ nommé, donc les critères de sélection ne sont vraiment pas nécessaires.

Et Cas 3 . Eh bien, c'est assez simple dans la mesure où si vous n'avez pas besoin pour conserver quoi que ce soit d'autre dans le tableau après avoir supprimé ce contenu (c'est-à-dire un $pull où les champs correspondant à ceci étaient les seuls choses là-dedans, ou un $unset d'ailleurs), alors ne plaisantez pas avec quoi que ce soit d'autre et juste effacez le tableau .

Il s'agit d'une distinction importante si vous considérez que selon le point pour clarifier si les documents avec le champ nommé où le uniquement éléments dans les tableaux imbriqués, et en effet que la clé nommée était seulement chose présente dans les documents.

Le raisonnement étant qu'utiliser $pull comme indiqué ici et dans ces conditions, vous obtiendrez :

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

Ou avec le $unset :

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

Les deux ne sont clairement pas souhaitables. Il est donc logique que le arrayToDelete le champ était le seul le contenu qui s'y trouvait, alors le moyen le plus logique de tout supprimer consiste simplement à remplacer le tableau par un tableau vide. Ou bien $unset toute la propriété du document.

Notez cependant que toutes ces "choses fantaisistes" ( à l'exception du $set bien sûr) exigent que vous ayez au moins MongoDB 3.6 disponible pour utiliser cette fonctionnalité.

Dans le cas où vous utilisez toujours une version plus ancienne de MongoDB (et à la date de rédaction, vous ne devriez vraiment pas l'être puisque votre support officiel s'épuise dans seulement 5 mois à compter de cette date), alors d'autres réponses existantes sur Comment mettre à jour plusieurs Les éléments de tableau dans mongodb sont en fait pour vous.