Actualización de una matriz anidada con MongoDB

Resuelto masanorinyo asked hace 10 años • 2 respuestas

Estoy intentando actualizar un valor en la matriz anidada pero no puedo hacerlo funcionar.

Mi objeto es así

 {
    "_id": {
        "$oid": "1"
    },
    "array1": [
        {
            "_id": "12",
            "array2": [
                  {
                      "_id": "123",
                      "answeredBy": [],   // need to push "success" 
                  },
                  {
                      "_id": "124",
                      "answeredBy": [],
                  }
             ],
         }
     ]
 }

Necesito enviar un valor a la matriz "respondida por".

En el siguiente ejemplo, intenté insertar la cadena "éxito" en la matriz "answeredBy" del objeto "123 _id" pero no funciona.

callback = function(err,value){
     if(err){
         res.send(err);
     }else{
         res.send(value);
     }
};
conditions = {
    "_id": 1,
    "array1._id": 12,
    "array2._id": 123
  };
updates = {
   $push: {
     "array2.$.answeredBy": "success"
   }
};
options = {
  upsert: true
};
Model.update(conditions, updates, options, callback);

encontré este enlace , pero su respuesta solo dice que debo usar una estructura similar a un objeto en lugar de una matriz. Esto no se puede aplicar en mi situación. Realmente necesito que mi objeto esté anidado en matrices.

Sería genial si pudieras ayudarme aquí. He pasado horas para resolver esto.

¡Gracias de antemano!

masanorinyo avatar May 10 '14 11:05 masanorinyo
Aceptado

Alcance general y explicación

Hay algunas cosas mal en lo que estás haciendo aquí. En primer lugar, las condiciones de su consulta. Te refieres a varios_id valores que no deberías necesitar y al menos uno de los cuales no está en el nivel superior.

Para obtener un valor "anidado" y también asumir que ese _idvalor es único y no aparecería en ningún otro documento, su formulario de consulta debería ser así:

Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Ahora bien, eso realmente funcionaría, pero en realidad es sólo una casualidad que lo haga, ya que hay muy buenas razones por las que no debería funcionar para usted.

La lectura importante se encuentra en la documentación oficial del operador posicional$ en el tema "Matrices anidadas". Lo que esto dice es:

El operador posicional $ no se puede utilizar para consultas que atraviesan más de una matriz, como consultas que atraviesan matrices anidadas dentro de otras matrices, porque el reemplazo del marcador de posición $ es un valor único.

Específicamente, lo que eso significa es que el elemento que coincidirá y se devolverá en el marcador de posición posicional es el valor del índice de la primera matriz coincidente. Esto significa, en su caso, el índice coincidente en la matriz de nivel "superior".

Entonces, si observa la notación de consulta como se muestra, hemos "codificado" la primera posición (o índice 0) en la matriz de nivel superior, y sucede que el elemento coincidente dentro de "matriz2" también es la entrada de índice cero.

Para demostrar esto, puede cambiar el _idvalor coincidente a "124" y el resultado será $pushuna nueva entrada en el elemento con _id"123", ya que ambos están en la entrada de índice cero de "array1" y ese es el valor devuelto al marcador de posición.

Ese es el problema general con las matrices anidadas. Podría eliminar uno de los niveles y aún podría acceder $pushal elemento correcto en su matriz "superior", pero aún habría varios niveles.

Intente evitar anidar matrices, ya que se encontrará con problemas de actualización, como se muestra.

El caso general es "aplanar" las cosas que "piensa" que son "niveles" y, de hecho, convertir estos "atributos" en los elementos de detalle finales. Por ejemplo, la forma "aplanada" de la estructura en la pregunta debería ser algo como:

 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

O incluso cuando se acepta que la matriz interna es $pushúnica y nunca se actualiza:

 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Ambos se prestan a actualizaciones atómicas dentro del alcance del operador posicional.$


MongoDB 3.6 y superior

A partir de MongoDB 3.6 hay nuevas funciones disponibles para trabajar con matrices anidadas. Esto utiliza la sintaxis filtrada posicional$[<identifier>] para hacer coincidir los elementos específicos y aplicar diferentes condiciones en arrayFiltersla declaración de actualización:

Model.update(
  {
    "_id": 1,
    "array1": {
      "$elemMatch": {
        "_id": "12","array2._id": "123"
      }
    }
  },
  {
    "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
  },
  {
    "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] 
  }
)

El método "arrayFilters"as pasado a las opciones para .update()o incluso .updateOne(), o especifica las condiciones que .updateMany()deben coincidir en el identificador proporcionado en la declaración de actualización. Se actualizarán todos los elementos que coincidan con la condición dada..findOneAndUpdate().bulkWrite()

Debido a que la estructura está "anidada", en realidad utilizamos "filtros múltiples" como se especifica con una "matriz" de definiciones de filtros como se muestra. El "identificador" marcado se utiliza para comparar con la sintaxis filtrada posicional$[<identifier>] realmente utilizada en el bloque de actualización de la declaración. En este caso inner, y outerson los identificadores utilizados para cada condición según lo especificado con la cadena anidada.

Esta nueva expansión hace posible la actualización del contenido de la matriz anidada, pero en realidad no ayuda con la practicidad de "consultar" dichos datos, por lo que se aplican las mismas advertencias explicadas anteriormente.

Por lo general, realmente "quieres" expresarlo como "atributos", incluso si tu cerebro inicialmente piensa en "anidar", generalmente es una reacción a cómo crees que las "partes relacionales anteriores" se unen. En realidad, realmente necesitas más desnormalización.

Consulte también Cómo actualizar varios elementos de la matriz en mongodb , ya que estos nuevos operadores de actualización en realidad coinciden y actualizan "múltiples elementos de la matriz" en lugar de solo el primero , que ha sido la acción anterior de las actualizaciones posicionales.

NOTA Irónicamente, dado que esto se especifica en el argumento "opciones" para .update()métodos similares y similares, la sintaxis generalmente es compatible con todas las versiones recientes del controlador.

Sin embargo, esto no es cierto para el mongoshell, ya que la forma en que se implementa el método allí ("irónicamente por compatibilidad con versiones anteriores") el arrayFiltersargumento no es reconocido y eliminado por un método interno que analiza las opciones para ofrecer "compatibilidad con versiones anteriores" con versiones anteriores. Versiones del servidor MongoDB y una .update()sintaxis de llamada API "heredada".

Entonces, si desea utilizar el comando en el mongoshell u otros productos "basados ​​en shell" (en particular, Robo 3T), necesita una versión más reciente de la rama de desarrollo o de la versión de producción a partir de 3.6 o superior.

Consulte también positional all $[]que también actualiza "múltiples elementos de la matriz", pero sin aplicar a las condiciones especificadas y se aplica a todos los elementos de la matriz donde esa es la acción deseada.

Neil Lunn avatar May 10 '2014 05:05 Neil Lunn

Sé que esta es una pregunta muy antigua, pero yo mismo luché con este problema y encontré lo que creo que es una mejor respuesta.

Una forma de resolver este problema es utilizar Sub-Documents. Esto se hace anidando esquemas dentro de sus esquemas.

MainSchema = new mongoose.Schema({
   array1: [Array1Schema]
})

Array1Schema = new mongoose.Schema({
   array2: [Array2Schema]
})

Array2Schema = new mongoose.Schema({
   answeredBy": [...]
})

De esta manera el objeto se verá como el que muestras, pero ahora cada matriz está llena de subdocumentos. Esto hace posible marcar su camino hacia el subdocumento que desee. En lugar de utilizar a .update, utilice a .findo .findOnepara obtener el documento que desea actualizar.

Main.findOne((
    {
        _id: 1
    }
)
.exec(
    function(err, result){
        result.array1.id(12).array2.id(123).answeredBy.push('success')
        result.save(function(err){
            console.log(result)
        });
    }
)

Yo no he usado la .push(función ) de esta manera, por lo que es posible que la sintaxis no sea correcta, pero he usado ambas .set()y .remove()y ambas funcionan perfectamente bien.

Jesper Pannerup avatar Oct 22 '2014 10:10 Jesper Pannerup