Dans mongoDb, comment supprimer un élément de tableau par son index?

97

Dans l'exemple suivant, supposons que le document se trouve dans la collection db.people .

Comment supprimer le 3ème élément du tableau des intérêts par son index ?

{
  "_id" : ObjectId("4d1cb5de451600000000497a"),           
  "name" : "dannie",  
  "interests" : [  
    "guitar",  
    "programming",           
    "gadgets",  
    "reading"  
  ]   
}

Voici ma solution actuelle:

var interests = db.people.findOne({"name":"dannie"}).interests;  
interests.splice(2,1)  
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});

Y a-t-il un moyen plus direct?

dannie.f
la source

Réponses:

138

Il n'y a pas de moyen simple de tirer / supprimer par index de tableau. En fait, il s'agit d'un numéro ouvert http://jira.mongodb.org/browse/SERVER-1014 , vous pouvez voter pour.

La solution de contournement utilise $ unset puis $ pull:

db.lists.update({}, {$unset : {"interests.3" : 1 }}) 
db.lists.update({}, {$pull : {"interests" : null}})

Mise à jour: comme mentionné dans certains commentaires, cette approche n'est pas atomique et peut provoquer des conditions de concurrence si d'autres clients lisent et / ou écrivent entre les deux opérations. Si nous avons besoin que l'opération soit atomique, nous pourrions:

  • Lire le document de la base de données
  • Mettez à jour le document et supprimez l'élément du tableau
  • Remplacez le document dans la base de données. Pour nous assurer que le document n'a pas changé depuis que nous l'avons lu, nous pouvons utiliser la mise à jour si le modèle actuel décrit dans la documentation mongo
Javier Ferrero
la source
33
Ça fait un an et demi? Est-ce toujours l'état des choses avec mongodb?
Abe
La réponse suivante est plus directe et a fonctionné pour moi. Bien qu'il ne s'agisse pas d'une suppression par index, mais plutôt d'une suppression par valeur.
vish le
1
@Javier - Cette solution ne vous laisserait-elle pas ouvert à des problèmes si la base de données changeait entre le moment où vous avez compté la position dans l'index et le moment où vous avez fait l'initialisation?
UpTheCreek
Ce n'est pas atomique, donc il est susceptible de provoquer des conditions de course obscures si vous avez un code qui n'est pas prêt à faire face à une entrée nulle dans le tableau.
Glenn Maynard
3
8 ans et ce ticket n'a toujours pas été implémenté ...
Olly John
19

Vous pouvez utiliser un $pullmodificateur d' updateopération pour supprimer un élément particulier d'un tableau. Dans le cas où vous avez fourni une requête ressemblera à ceci:

db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})

Vous pouvez également envisager d'utiliser $pullAllpour supprimer toutes les occurrences. Plus d'informations à ce sujet sur la page de documentation officielle - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Cela n'utilise pas l'index comme critère de suppression d'un élément, mais peut toujours aider dans des cas similaires au vôtre. IMO, l'utilisation d'index pour adresser des éléments à l'intérieur d'un tableau n'est pas très fiable car mongodb n'est pas cohérent sur un ordre d'éléments aussi fas que je le sais.

Sunseeker
la source
6
Son élément est un tableau dans la question. Mongo est cohérent sur commande dans ce cas. En fait, tous les documents sont en fait des collections ordonnées mais de nombreux pilotes ne les gèrent pas de cette manière. Pour compliquer davantage les choses, si un document doit être déplacé après une opération de mise à jour (en raison de la croissance au-delà du facteur de remplissage), l'ordre des touches devient soudainement alphabétique (sous les couvertures, je pense qu'elles sont triées par leur valeur binaire, ce soupçon est basé sur ma connaissance que les tableaux sont à peu près des documents standard avec des clés qui sont des entiers consécutifs au lieu de chaînes).
mars75
Cela évite les problèmes de unset + pull, mais nécessite que votre dictionnaire soit strictement ordonné. Les dictionnaires de la plupart des langues ne sont pas ordonnés, il peut donc être difficile d'y arriver.
Glenn Maynard
@ marr75: Avez-vous une référence? Les documents Mongo sont toujours censés conserver l'ordre, et si jamais il réorganisait les sous-documents, beaucoup de choses se briseraient.
Glenn Maynard
Je n'ai pas de référence. Je le sais par expérience personnelle avec la version 2.2, ayant corrigé des bogues introduits par des documents mis à jour et déplacés. Je n'ai pas beaucoup travaillé sur mongo ces derniers mois, donc je ne peux pas parler des versions plus récentes.
marr75
En ce qui concerne la cohérence des éléments, mon cas d'utilisation souhaité est. Un client demande json à mongo, puis si le client choisit de dire «supprimer» un élément d'un tableau json, il transmettra l'index du tableau au serveur à supprimer. si la position des éléments du tableau était déplacée, ce serait bizarre, mais expliquerait pourquoi ils ne
peuvent
4

Plutôt que d'utiliser unset (comme dans la réponse acceptée), je résous ce problème en définissant le champ sur une valeur unique (c'est-à-dire non NULL), puis en tirant immédiatement cette valeur. Un peu plus sûr du point de vue asynchrone. Voici le code:

    var update = {};
    var key = "ToBePulled_"+ new Date().toString();
    update['feedback.'+index] = key;
    Venues.update(venueId, {$set: update});
    return Venues.update(venueId, {$pull: {feedback: key}});

Espérons que mongo résoudra ce problème, peut-être en étendant le modificateur $ position pour prendre en charge $ pull ainsi que $ push.

Stephen Orr
la source
3

Je recommanderais d'utiliser un champ GUID (j'ai tendance à utiliser ObjectID), ou un champ auto-incrémenté pour chaque sous-document du tableau.

Avec ce GUID, il est facile d'émettre un $ pull et de s'assurer que le bon sera extrait. Il en va de même pour les autres opérations sur les tableaux.

Climax
la source
2

À partir de Mongo 4.4, le$function opérateur d'agrégation permet d'appliquer une fonction javascript personnalisée pour implémenter un comportement non pris en charge par le langage de requête MongoDB.

Par exemple, pour mettre à jour un tableau en supprimant un élément à un index donné:

// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
  { "name": "dannie" },
  [{ $set:
    { "interests":
      { $function: {
          body: function(interests) { interests.splice(2, 1); return interests; },
          args: ["$interests"],
          lang: "js"
      }}
    }
  }]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }

$function prend 3 paramètres:

  • body, qui est la fonction à appliquer, dont le paramètre est le tableau à modifier. La fonction ici consiste simplement à utiliser splice pour supprimer 1 élément à l'index 2.
  • args, qui contient les champs de l'enregistrement que la bodyfonction prend comme paramètre. Dans notre cas "$interests".
  • lang, qui est la langue dans laquelle la bodyfonction est écrite. Seulement jsest actuellement disponible.
Xavier Guihot
la source
2

dans Mongodb 4.2, vous pouvez faire ceci:

db.example.update({}, [
     {$set: {field: {
           $concatArrays: [ 
                  {$slice: ["$field", P]}, 
                  {$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
           ]
     }}}
]);

P est l'index de l'élément que vous souhaitez supprimer du tableau.

Si vous souhaitez supprimer de P jusqu'à la fin:

db.example.update({}, [
     {$set: {field: {$slice: ["$field", P]}}
]);
Ali
la source
0

Pour les personnes qui recherchent une réponse en utilisant mangouste avec nodejs. Voilà comment je fais.

exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my  other field in my db

Test.findOneAndUpdate({ tCodigo: codTest },
    {
        '$unset': {
            [inPregunta]: 1, // put the field with [] 
            [inOpciones]: 1,
            [inTipo]: 1
        }
    }).then(()=>{ 
    Test.findOneAndUpdate({ tCodigo: codTest }, {
        '$pull': {
            'tPreguntas.0.pregunta': null,
            'tPreguntas.0.opciones': null,
            'tPreguntas.0.tipo': null
        }
    }).then(testModificado => {
        if (!testModificado) {
            res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
        } else {
            res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
        }
    })}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
 }

Je peux réécrire cette réponse si elle ne comprend pas très bien, mais je pense que ça va.

J'espère que cela vous aidera, j'ai perdu beaucoup de temps face à ce problème.

Schwarz54
la source
-3

Au lieu d'utiliser $ pull, nous pouvons utiliser $ pop pour supprimer des éléments d'un tableau par son index. Mais vous devez soustraire 1 de la position d'index pour supprimer en fonction de l'index.

Par exemple, si vous souhaitez supprimer l'élément de l'index 0, vous devez utiliser -1, pour l'index 1, vous devez utiliser 0 et ainsi de suite ...

Requête pour supprimer le 3e élément (gadgets):

db.people.update({"name":"dannie"}, {'$pop': {"interests": 1}})

pour référence: https://docs.mongodb.com/manual/reference/operator/update/pop/

Mohan Krishnan
la source
3
Selon la documentation liée, $popne peut supprimer que le premier ou le dernier élément du tableau. La requête de cette réponse ne supprime pas le troisième élément.
Travis G.