No mongoDb, como você remove um elemento da matriz por seu índice?

97

No exemplo a seguir, suponha que o documento esteja na coleção db.people .

Como remover o terceiro elemento da matriz de interesses por seu índice ?

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

Esta é minha solução atual:

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

Existe uma maneira mais direta?

dannie.f
fonte

Respostas:

138

Não há uma maneira direta de puxar / remover por índice de array. Na verdade, este é um problema aberto http://jira.mongodb.org/browse/SERVER-1014 , você pode votar nele.

A solução alternativa é usar $ unset e, em seguida, $ pull:

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

Atualização: conforme mencionado em alguns dos comentários, esta abordagem não é atômica e pode causar algumas condições de corrida se outros clientes lerem e / ou escreverem entre as duas operações. Se precisarmos que a operação seja atômica, poderíamos:

  • Leia o documento do banco de dados
  • Atualize o documento e remova o item da matriz
  • Substitua o documento no banco de dados. Para garantir que o documento não tenha mudado desde a sua leitura, podemos usar o padrão update if atual descrito nos documentos do mongo
Javier Ferrero
fonte
33
Já faz um ano e meio? Este ainda é o estado de coisas com mongodb?
Abe
A próxima resposta é mais direta e funcionou para mim. Embora não seja removendo por índice, mas sim removendo por valor.
vish
1
@Javier - essa solução não deixaria você aberto a problemas se o banco de dados mudasse entre o momento em que você contou a posição no índice e o momento em que você desmarcou?
UpTheCreek
Isso não é atômico, portanto, é provável que cause condições de corrida obscuras se você tiver qualquer código que não esteja preparado para lidar com uma entrada nula no array.
Glenn Maynard
3
8 anos e este tíquete ainda não foi implementado ...
Olly John
19

Você pode usar o $pullmodificador de updateoperação para remover um elemento específico em uma matriz. Caso você tenha fornecido uma consulta será semelhante a esta:

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

Além disso, você pode considerar o uso $pullAllpara remover todas as ocorrências. Mais sobre isso na página de documentação oficial - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Isso não usa índice como critério para remover um elemento, mas ainda pode ajudar em casos semelhantes ao seu. IMO, usar índices para endereçar elementos dentro de um array não é muito confiável, pois mongodb não é consistente em uma ordem de elementos tão rápido quanto eu sei.

Sunseeker
fonte
6
Seu elemento é uma matriz na questão. O Mongo é consistente na ordem neste caso. Na verdade, todos os documentos são coleções ordenadas, mas muitos drivers não os tratam dessa maneira. Para complicar ainda mais o assunto, se um documento tiver que ser movido após uma operação de atualização (devido ao crescimento além do fator de preenchimento), a ordem das chaves torna-se repentinamente alfabética (por baixo das capas, acho que são classificadas por seu valor binário, essa suspeita é com base no meu conhecimento, os arrays são praticamente documentos padrão com chaves que são inteiros consecutivos em vez de strings).
março de 75
Isso evita os problemas de unset + pull, mas requer que seu dicionário seja estritamente ordenado. Os dicionários na maioria dos idiomas não são ordenados, por isso pode ser uma pena fazer isso acontecer.
Glenn Maynard
@ marr75: Você tem uma referência? Os documentos do Mongo sempre devem manter a ordem e, se ele reordenar os subdocumentos, muitas coisas serão quebradas.
Glenn Maynard
Eu não tenho uma referência. Eu sei disso por experiência própria com o 2.2, tendo corrigido bugs que foram introduzidos por documentos atualizados e movidos. Não fiz muito trabalho com o mongo nos últimos meses, então não posso falar sobre versões mais atuais.
mar 75
No que diz respeito à consistência do elemento, meu caso de uso desejado é. Um cliente solicita json de mongo e, em seguida, se o cliente escolher dizer, 'excluir' um item de uma matriz json, ele passará o índice da matriz para o servidor a ser excluído. se a posição dos itens da matriz mudasse, isso seria estranho, mas explicaria por que eles não podem implementar o recurso
meffect
4

Em vez de usar o não definido (como na resposta aceita), resolvo isso definindo o campo com um valor único (ou seja, não NULL) e imediatamente puxando esse valor. Um pouco mais seguro de uma perspectiva assincrônica. Aqui está o código:

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

Esperançosamente, mongo resolverá isso, talvez estendendo o modificador de posição $ para suportar $ pull, bem como $ push.

Stephen Orr
fonte
3

Eu recomendaria usar um campo GUID (eu costumo usar ObjectID), ou um campo de incremento automático para cada subdocumento na matriz.

Com este GUID é fácil emitir um $ pull e ter certeza de que o correto será puxado. O mesmo vale para outras operações de array.

Clímax
fonte
2

A partir de Mongo 4.4, o $functionoperador de agregação permite aplicar uma função javascript customizada para implementar um comportamento não suportado pela linguagem de consulta MongoDB.

Por exemplo, para atualizar uma matriz removendo um elemento em um determinado índice:

// { "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 leva 3 parâmetros:

  • body, que é a função a ser aplicada, cujo parâmetro é a matriz a ser modificada. A função aqui consiste simplesmente em usar splice para remover 1 elemento no índice 2.
  • args, que contém os campos do registro que a bodyfunção usa como parâmetro. No nosso caso "$interests".
  • lang, que é o idioma no qual a bodyfunção é escrita. Apenas jsestá disponível atualmente.
Xavier Guihot
fonte
2

no Mongodb 4.2, você pode fazer isso:

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

P é o índice do elemento que você deseja remover do array.

Se você deseja remover de P até o final:

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

Para pessoas que estão procurando uma resposta usando o mangusto com nodejs. É assim que eu faço.

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 }); });
 }

Posso reescrever esta resposta se não entender muito bem, mas acho que está tudo bem.

Espero que isso ajude você, perdi muito tempo enfrentando esse problema.

Schwarz54
fonte
-3

Em vez de usar $ pull, podemos usar $ pop para remover elementos em um array por seu índice. Mas você deve subtrair 1 da posição do índice para remover com base no índice.

Por exemplo, se você deseja remover o elemento no índice 0 você deve usar -1, para o índice 1 você deve usar 0 e assim por diante ...

Consulta para remover o terceiro elemento (gadgets):

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

para referência: https://docs.mongodb.com/manual/reference/operator/update/pop/

Mohan Krishnan
fonte
3
De acordo com a documentação vinculada, $popsó pode remover o primeiro ou o último elemento da matriz. A consulta nesta resposta não remove o terceiro elemento.
Travis G.