1

After searching, I was unable to figure out how to perform multiple updates to a single field.

I have a document with a "tags" array field. Every document will have random tags before I begin the update. In a single operation, I want to add some tags and remove some tags.

The following update operator returns an error "Invalid modifier specified: $and"

    updateOperators: { "$and" : [
      { "$addToSet" : { "tags" : { "$each" : [ "tag_1" , "tag_2"]}}},
      { "$pullAll" : { "tags" : [ "tag_2", "tag_3"]}}]}  

    collection.update(query, updateOperators, multi=true)  

How do I both add and remove values to an array in a single operation, to multiple documents?

user1170533
  • 79
  • 1
  • 5
  • Currently, my plan is to made two mongo calls since that will be less expensive than making a call for every document to get the current list of values in the array. – user1170533 Aug 12 '14 at 22:14

2 Answers2

2

You don't need the $and with the update query, but you cannot update two fields at the same time with an update - as you would see if you tried the following in the shell:

db.test.update({}, { "$addToSet" : { "tags" : { "$each" : [ "tag_1" , "tag_2"]}}, 
                     "$pullAll"  : { "tags" : [ "tag_2", "tag_3"] }}, true, false)

You would get a Cannot update 'tags' and 'tags' at the same time error message. So how to achieve this? Well with this schema you would need to do it in multiple operations, you could use the new bulk operation api as shown below (shell):

var bulk = db.coll.initializeOrderedBulkOp();
bulk.find({ "tags": 1 }).updateOne({ "$addToSet": { "$each" : [ "tag_1" , "tag_2"]}});
bulk.find({ "tags": 1 }).updateOne({ "$pullAll": { "tags": [ "tag_2", "tag_3"] } });
bulk.execute();

Or in Casbah with the dsl helpers:

val bulk = collection.initializeOrderedBulkOperation
bulk.find(MongoDBObject("tags" -> 1)).updateOne($addToSet("tags") $each("tag_1", tag_2"))
bulk.find(MongoDBObject("tags" -> 1)).updateOne($pullAll("tags" -> ("tags_2", "tags_3")))
bulk.execute()

Its not atomic and there is no guarantee that nothing else will try to modify, but it is as close as you will currently get.

Ross
  • 17,861
  • 2
  • 55
  • 73
1

Mongo does atomic updates so you could just construct the tags you want in the array and then replace the entire array.

I would advise against using an array to store these values all together as this is an "unbound" array of tags. Unbound arrays cause movement on disk and that causes indexes to be updated and the OS and mongo to do work.

Instead you should store each tag as a seperate document in a different collection and "bucket" them based on the _id of the related document.

Example

{_id : <_id> <key> <value>} - single docuemnt

This will allow you to query for all the tags for a single user with db.collection.find({_id : /^<_id>/}) and bucket the results.

Sam
  • 21
  • 3
  • Interesting suggestion. Unfortunately, I won't know the existing tags before I run this update. For example, if some of the documents have "tag_5" and some have "tag_6", then wouldn't I have to read every document to keep the tags not affected by this update? Yes, the tags are an unbound, uncontrolled array of values; but it is currently required. Good consideration for future changes. – user1170533 Aug 12 '14 at 22:01
  • Yes for the atomic update you would. If you use separate documents you could just issue a delete or insert. It is not possible to issue more than 1 operation on a field at a time. – Sam Aug 12 '14 at 22:14