En mongoDb, ¿cómo se elimina un elemento de matriz por su índice?

97

En el siguiente ejemplo, suponga que el documento está en la colección db.people .

¿Cómo eliminar el tercer elemento de la matriz de intereses por su índice ?

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

Esta es mi solución actual:

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

¿Existe una forma más directa?

dannie.f
fuente

Respuestas:

138

No hay una forma directa de extraer / eliminar por índice de matriz. De hecho, este es un tema abierto http://jira.mongodb.org/browse/SERVER-1014 , puedes votarlo.

La solución alternativa es usar $ unset y luego $ pull:

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

Actualización: como se menciona en algunos de los comentarios, este enfoque no es atómico y puede causar algunas condiciones de carrera si otros clientes leen y / o escriben entre las dos operaciones. Si necesitamos que la operación sea atómica, podríamos:

  • Leer el documento de la base de datos
  • Actualice el documento y elimine el elemento de la matriz
  • Reemplazar el documento en la base de datos. Para asegurarnos de que el documento no haya cambiado desde que lo leímos, podemos usar la actualización si el patrón actual se describe en los documentos de mongo
Javier Ferrero
fuente
33
¿Ha pasado año y medio? ¿Sigue siendo este el estado de cosas con mongodb?
Abe
La siguiente respuesta es más directa y me funcionó. Aunque no se elimina por índice, sino que elimina por valor.
vish
1
@Javier: ¿esta solución no lo dejaría abierto a problemas si la base de datos cambiara entre el momento en que contó la posición en el índice y el momento en que hizo el desarmado?
UpTheCreek
Esto no es atómico, por lo que es probable que cause condiciones de carrera oscuras si tiene algún código que no esté preparado para hacer frente a una entrada nula en la matriz.
Glenn Maynard
3
8 años y este ticket aún no se ha implementado ...
Olly John
19

Puede usar el $pullmodificador de updateoperación para eliminar un elemento particular en una matriz. En caso de que haya proporcionado una consulta, se verá así:

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

Además, puede considerar usar $pullAllpara eliminar todas las apariciones. Más sobre esto en la página de documentación oficial: http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Esto no usa el índice como criterio para eliminar un elemento, pero aún así podría ayudar en casos similares al suyo. En mi opinión, el uso de índices para direccionar elementos dentro de una matriz no es muy confiable ya que mongodb no es consistente en un orden de elementos tan rápido como yo lo sé.

Sunseeker
fuente
6
Su elemento es una matriz en la pregunta. Mongo es consistente en orden en este caso. De hecho, todos los documentos son colecciones ordenadas, pero muchos conductores no los manejan de esta manera. Para complicar aún más el asunto, si un documento tiene que moverse después de una operación de actualización (debido al crecimiento más allá del factor de relleno), el orden de las teclas de repente se vuelve alfabético (bajo las cubiertas, creo que están ordenados por su valor binario, esta sospecha es según mi conocimiento de que las matrices son documentos prácticamente estándar con claves que son números enteros consecutivos en lugar de cadenas).
mar75
Esto evita los problemas de unset + pull, pero requiere que su diccionario esté estrictamente ordenado. Los diccionarios de la mayoría de los idiomas están desordenados, por lo que puede ser complicado hacer que esto suceda.
Glenn Maynard
@ marr75: ¿Tiene alguna referencia? Se supone que los documentos de Mongo siempre deben mantener el orden, y si alguna vez se reordenan los subdocumentos, muchas cosas se romperían.
Glenn Maynard
No tengo una referencia. Lo sé por experiencia personal con 2.2, habiendo corregido errores que fueron introducidos por documentos actualizados y movidos. No he hecho mucho trabajo con mongo en los últimos meses, así que no puedo hablar de versiones más actuales.
marr75
En cuanto a la consistencia del elemento, mi caso de uso deseado es. Un cliente solicita json de mongo y luego, si el cliente elige decir, 'eliminar' un elemento de una matriz json, pasará el índice de la matriz al servidor para ser eliminado. si la posición de los elementos de la matriz se moviera, sería extraño, pero explicaría por qué no pueden implementar la función
meffect
4

En lugar de usar el no establecido (como en la respuesta aceptada), resuelvo esto configurando el campo en un valor único (es decir, no NULL) y luego inmediatamente extrayendo ese valor. Un poco más seguro desde una perspectiva asincrónica. Aquí está el 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}});

Con suerte, mongo abordará esto, tal vez extendiendo el modificador $ position para admitir $ pull y $ push.

Stephen Orr
fuente
3

Recomendaría usar un campo GUID (tiendo a usar ObjectID), o un campo de incremento automático para cada subdocumento en la matriz.

Con este GUID es fácil emitir un $ pull y asegurarse de que se extraiga el correcto. Lo mismo ocurre con otras operaciones de matriz.

Clímax
fuente
2

A partir de Mongo 4.4, el $functionoperador de agregación permite aplicar una función javascript personalizada para implementar el comportamiento no admitido por el lenguaje de consulta MongoDB.

Por ejemplo, para actualizar una matriz eliminando un elemento en un índice determinado:

// { "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 toma 3 parámetros:

  • body, que es la función a aplicar, cuyo parámetro es la matriz a modificar. La función aquí simplemente consiste en usar empalme para eliminar 1 elemento en el índice 2.
  • args, que contiene los campos del registro que la bodyfunción toma como parámetro. En nuestro caso "$interests".
  • lang, que es el idioma en el que bodyestá escrita la función. Solo jsestá disponible actualmente.
Xavier Guihot
fuente
2

en Mongodb 4.2 puedes hacer esto:

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

P es el índice del elemento que desea eliminar de la matriz.

Si desea eliminar de P hasta el final:

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

Para las personas que buscan una respuesta usando mangosta con nodejs. Así es como lo hago.

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

Puedo reescribir esta respuesta si no la entiendo muy bien, pero creo que está bien.

Espero que esto te ayude, he perdido mucho tiempo enfrentando este problema.

Schwarz54
fuente
-3

En lugar de usar $ pull, podemos usar $ pop para eliminar elementos en una matriz por su índice. Pero debe restar 1 de la posición del índice para eliminar según el índice.

Por ejemplo, si desea eliminar el elemento en el índice 0, debe usar -1, para el índice 1 debe usar 0 y así sucesivamente ...

Consulta para eliminar el tercer elemento (gadgets):

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

para referencia: https://docs.mongodb.com/manual/reference/operator/update/pop/

Mohan Krishnan
fuente
3
Según la documentación vinculada, $popsolo se puede eliminar el primer o último elemento de la matriz. La consulta en esta respuesta no elimina el tercer elemento.
Travis G.