2012-05-09 21 views
143

Supongamos que tenemos la siguiente colección, que tengo algunas preguntas acerca de:MongoDB - Actualización de objetos en serie (actualización anidada) de un documento

{ 
    "_id" : ObjectId("4faaba123412d654fe83hg876"), 
    "user_id" : 123456, 
    "total" : 100, 
    "items" : [ 
      { 
        "item_name" : "my_item_one", 
        "price" : 20 
      }, 
      { 
        "item_name" : "my_item_two", 
        "price" : 50 
      }, 
      { 
        "item_name" : "my_item_three", 
        "price" : 30 
      } 
    ] 
} 

1 - Quiero aumentar el precio de "item_name":" my_item_two "y si no existe, debe adjuntarse a la matriz de" elementos ".

2 - ¿Cómo puedo actualizar dos campos al mismo tiempo. Por ejemplo, aumente el precio de "my_item_three" y al mismo tiempo aumente el "total" (con el mismo valor).

Prefiero hacer esto en el lado MongoDB, de lo contrario tengo que cargar el documento en el lado del cliente (Python) y construir el documento actualizado y reemplazarlo por el existente en MongoDB.

ACTUALIZACIÓN Esto es lo que he probado y funciona bien si el objeto existe:

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}}) 

Pero si no existe la clave no hace nada. También solo actualiza el objeto anidado. No hay forma con este comando de actualizar el campo "total" también.

+1

Creo que no puedes hacer esto en mongo, excepto tal vez con mucho dolor usando el eval. Mongo es muy limitado en operaciones de datos. –

+3

@Haapala: mongodb tiene $ inc y actualiza con upsert – jdi

+1

@jdi sí sí, pero no ayuda mucho aquí, pero lo que necesita es múltiples $ incs, condicionalmente, y si el elemento no existe, entonces un $ push es necesario. –

Respuesta

173

Para la pregunta n. ° 1, divídalo en dos partes. Primero, incremente cualquier documento que tenga "items.item_name" igual a "my_item_two". Para esto, deberá usar el operador posicional "$". Algo así como:

db.bar.update({user_id : 123456 , "items.item_name" : "my_item_two" } , 
       {$inc : {"items.$.price" : 1} } , 
       false , 
       true); 

Tenga en cuenta que esto sólo aumentará el primer subdocumento emparejado en cualquier matriz (por lo que si usted tiene otro documento en la matriz con "item_name" igual a "my_item_two", que no conseguirá incrementado) . Pero esto podría ser lo que quieras.

La segunda parte es más complicada. Podemos empujar un nuevo elemento a una matriz sin un "my_item_two" de la siguiente manera:

db.bar.update({user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
       {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } , 
       false , 
       true); 

de tu pregunta # 2, la respuesta es fácil. Para incrementar el total y el precio de item_three en cualquier documento que contenga "my_item_three", puede usar el operador $ inc en múltiples campos al mismo tiempo. Algo como:

db.bar.update({"items.item_name" : {$ne : "my_item_three" }} , 
       {$inc : {total : 1 , "items.$.price" : 1}} , 
       false , 
       true); 
+0

Gracias por la respuesta. La respuesta para la Pregunta # 2 es perfecta y funciona bien :) Pero para la Pregunta # 1, el problema es que debe buscar el documento por "user_id", y no por "items.item_name". En este caso, no puedo usar el operador posicional, ya que no he especificado la matriz en la consulta de búsqueda. También quiero que ambas partes se hagan de una vez. (si es posible) – Majid

+0

Buenos ejemplos. Sentí que estaba haciendo esta dirección aparente de los enlaces en mi respuesta, pero seguí recibiendo resistencia diciendo que no era lo que estaba buscando. Supongo que el OP solo necesitaba ver ejemplos sobre cómo hacer múltiples $ inc. – jdi

+0

Para la pregunta n. ° 1, aún debería poder hacerlo en dos partes agregando el user_id al bit de consulta de cada una de las actualizaciones (modificaré la respuesta en consecuencia). Tendrá que pensar más acerca de si es posible hacerlo en una sola toma. – matulef

19

No hay forma de hacer esto en una sola consulta. Usted tiene que buscar el documento en la primera consulta:

Si existe documento:

db.bar.update({user_id : 123456 , "items.item_name" : "my_item_two" } , 
       {$inc : {"items.$.price" : 1} } , 
       false , 
       true); 

Else

db.bar.update({user_id : 123456 } , 
       {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } , 
       false , 
       true); 

hay necesidad de añadir condición {$ne : "my_item_two" }.

También en el entorno multiproceso debe tener cuidado de que solo un hilo pueda ejecutar el segundo (inserte el caso, si el documento no se encontró) a la vez, de lo contrario se insertarán documentos duplicados.

+1

no, hay una manera de hacerlo en una sola consulta, ver arriba –

+0

@MartijnScheffer, no veo una manera de hacer esto como una sola consulta en este hilo. Incluso la "respuesta" está usando 2 consultas iguales que en esta respuesta. ¿Me estoy perdiendo de algo? También espero que haya una manera de hacer esto en una consulta para evitar problemas de multi threading – dferraro

+0

@Udit, ¿cómo puedo usar los artículos. $. Precio dentro de la condición? cuando trato de agregar una condición como "incrementar solo si el precio es mayor a 0", no funciona; básicamente, nada se actualiza. Puedo usar esto en la condición de dónde, o me estoy perdiendo algo? artículos. $. precio: {$ gt: 0} – dferraro

Cuestiones relacionadas