web-dev-qa-db-fra.com

Comment mettre à jour plusieurs éléments de tableau dans mongodb

J'ai un document Mongo qui contient un tableau d'éléments.

J'aimerais réinitialiser l'attribut .handled de tous les objets du tableau où .profile = XX.

Le document est sous la forme suivante: 

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

alors, j'ai essayé ce qui suit:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

Cependant, il ne met à jour que l'élément de tableau correspondant à first dans chaque document. (C'est le comportement défini pour $ - l'opérateur de position .)

Comment puis-je mettre à jour tous les éléments de tableau correspondants?

153
LiorH

Pour le moment, il n'est pas possible d'utiliser l'opérateur de position pour mettre à jour tous les éléments d'un tableau. Voir JIRA http://jira.mongodb.org/browse/SERVER-1243

En travaillant autour de vous, vous pouvez:

  • Mettez à jour chaque élément individuellement (Events.0.handled events.1.handled ...) ou ...
  • Lisez le document, effectuez les modifications Manuellement et enregistrez-le en remplaçant le plus ancien. (Cochez "Mettre à jour si Actuel" si vous voulez vous assurer que Mises à jour atomiques)
103
Javier Ferrero

Ce qui a fonctionné pour moi a été ceci:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

Je pense que c'est plus clair pour les novices Mongo et tous ceux qui connaissent JQuery & friends.

60
Daniel Cerecedo

Avec la version de MongoDB 3.6 (et disponible dans MongoDB 3.5.12 dans la branche développement), vous pouvez désormais mettre à jour plusieurs éléments de tableau en une seule requête.

Ceci utilise la syntaxe d'opérateur positionné filtré $[<identifier>] update introduite dans cette version:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

Le "arrayFilters" tel que transmis aux options de la méthode .update() ou même .updateOne() , .updateMany() , .findOneAndUpdate() ou .bulkWrite() méthode spécifie les conditions de correspondance sur l'identificateur donné dans la déclaration de mise à jour. Tous les éléments correspondant à la condition donnée seront mis à jour.

Notant que le "multi" tel qu’indiqué dans le contexte de la question a été utilisé dans l’espoir que cela "mettrait à jour plusieurs éléments", mais ce n’était pas et n’est toujours pas le cas. Son utilisation ici s’applique à "plusieurs documents" comme cela a toujours été le cas ou est maintenant spécifié autrement comme paramètre obligatoire de .updateMany() dans les versions modernes de l’API.

NOTE Assez ironiquement, comme cela est spécifié dans l'argument "options" de .update() et des méthodes similaires, la syntaxe est généralement compatible avec toutes les versions de pilotes de versions récentes.

Cependant, cela n’est pas le cas pour le shell mongo, puisque la méthode est implémentée ("ironiquement pour la compatibilité ascendante"), l’argument arrayFilters n’est pas reconnu et supprimé par une méthode interne qui analyse les options afin de fournir la "compatibilité ascendante". avec les versions antérieures du serveur MongoDB et une syntaxe d'appel "héritée" de l'API .update().

Ainsi, si vous souhaitez utiliser la commande dans le shell mongo ou d'autres produits "à base de shell" (notamment Robo 3T), vous devez disposer d'une version la plus récente de la branche de développement ou de la version de production à partir de 3.6.

Voir aussi positional all $[] qui met également à jour "plusieurs éléments de tableau" mais sans appliquer les conditions spécifiées et s’applique aux éléments tous du tableau où il s’agit de l’action souhaitée.

Voir également Mise à jour d'un tableau imbriqué avec MongoDB pour savoir comment ces nouveaux opérateurs de position s'appliquent aux structures de tableau "imbriquées", où "les tableaux se trouvent dans d'autres tableaux".

IMPORTANT - Les installations mises à niveau à partir de versions précédentes "peuvent" ne pas avoir activé les fonctionnalités de MongoDB, ce qui peut également entraîner l'échec des instructions. Vous devez vous assurer que votre procédure de mise à niveau est complète avec des détails tels que les mises à niveau d'index, puis exécuter 

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

Cela a permis d'activer des fonctionnalités telles que les nouveaux opérateurs de mise à jour de position et autres. Vous pouvez également vérifier avec:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

Pour retourner le réglage actuel

38
Neil Lunn

Cela peut également être accompli avec une boucle while qui vérifie s'il reste des documents contenant encore des sous-documents non mis à jour. Cette méthode préserve le caractère atomique de vos mises à jour (ce que de nombreuses autres solutions ne font pas ici).

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

Le nombre de fois que la boucle est exécutée sera égal au nombre maximum de fois où les sous-documents avec profile égal à 10 et handled différent de 0 se produisent dans l'un des documents de votre collection. Ainsi, si vous avez 100 documents dans votre collection et que l'un d'entre eux a trois sous-documents qui correspondent à query et que tous les autres documents ont moins de sous-documents correspondants, la boucle s'exécutera trois fois.

Cette méthode évite le risque de supprimer d'autres données susceptibles d'être mises à jour par un autre processus pendant l'exécution de ce script. Cela minimise également la quantité de données transférées entre le client et le serveur.

18
Sean

Je suis étonné que cela n'ait toujours pas été abordé dans Mongo. Dans l’ensemble, le mongo ne semble pas très bien s’agissant des sous-tableaux. Vous ne pouvez pas compter les sous-tableaux simplement par exemple.

J'ai utilisé la première solution de Javier. Lisez le tableau dans les événements, puis parcourez et construisez l'ensemble exp:

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

Ceci peut être résumé dans une fonction utilisant un rappel pour le test conditionnel

8
ljelewis

Le fil est très vieux, mais je suis venu chercher une réponse ici, fournissant ainsi une nouvelle solution.

Avec MongoDB version 3.6+, il est maintenant possible d'utiliser l'opérateur de position pour mettre à jour tous les éléments d'un tableau. Voir documentation officielle ici .

La requête suivante fonctionnerait pour la question posée ici. J'ai également vérifié avec le pilote Java-MongoDB et cela fonctionne avec succès.

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

J'espère que cela aide quelqu'un comme moi.

1
ersnh

S'il vous plaît être conscient que certaines réponses dans ce fil suggérant l'utilisation de $ [] est FAUX.

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

Le code ci-dessus mettra à jour "manipulé" à 0 pour tous les éléments du tableau "events", quelle que soit sa valeur "profile". La requête {"events.profile":10} ne sert qu'à filtrer le document entier, pas les documents du tableau. Dans cette situation, il est indispensable d'utiliser $[elem] avec arrayFilters pour spécifier la condition des éléments du tableau afin que la réponse de Neil Lunn soit correcte.

0
Wenda Hu

L'opérateur $ [] sélectionne tous les tableaux imbriqués ..Vous pouvez mettre à jour tous les éléments du tableau avec '$ []'

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

référence

0
Beyaz

En fait, la commande de sauvegarde ne concerne que l’instance de Document Class . Elle comporte beaucoup de méthodes et d’attributs. Afin que vous puissiez utiliser maigre() fonction pour réduire la charge de travail . Reportez-vous ici. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Un autre problème avec la fonction de sauvegarde, qui créera des données en conflit avec plusieurs sauvegardes en même temps .Model.Update rendra les données cohérentes . Donc, pour mettre à jour plusieurs éléments dans un tableau de document. Utilisez votre langage de programmation familier et essayez quelque chose comme ceci, j'utilise mangouste dans ce sens:

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})
0
user3176403

J'ai essayé le suivant et ça fonctionne bien.

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// fonction de rappel dans le cas de nodejs

0
Pranay Saha