3

I have data that looks like this:

{
  "_id": ObjectId("4d525ab2924f0000000022ad"),
  "array": [
    { id: 1, other: 23 },
    { id: 2, other: 21 },
    { id: 0, other: 235 },
    { id: 3, other: 765 }
  ],
  "zeroes": []
}

I'm would like to to $pull an element from one array and $push it to a second array within the same document to result in something that looks like this:

{
  "_id": ObjectId("id"), 
  "array": [
    { id: 1, other: 23 },
    { id: 2, other: 21 },
    { id: 3, other: 765 }
  ],
  "zeroes": [
    { id: 0, other: 235 }
  ]
}

I realize that I can do this by doing a find and then an update, i.e.

db.foo.findOne({"_id": param._id})
.then((doc)=>{
  db.foo.update(
    {
      "_id": param._id
    },
    {
      "$pull": {"array": {id: 0}},
      "$push": {"zeroes": {doc.array[2]} }
    }
  )
})

I was wondering if there's an atomic function that I can do this with. Something like,

db.foo.update({"_id": param._id}, {"$move": [{"array": {id: 0}}, {"zeroes": 1}]}

Found this post that generously provided the data I used, but the question remains unsolved after 4 years. Has a solution to this been crafted in the past 4 years?

Move elements from $pull to another array

1 Answer 1

3

There is no $move in MongoDB. That being said, the easiest solution is a 2 phase approach:

  1. Query the document
  2. Craft the update with a $pull and $push/$addToSet

The important part here, to make sure everything is idempotent, is to include the original array document in the query for the update.

Given a document of the following form:

{
    _id: "foo",
    arrayField: [
        {
            a: 1,
            b: 1
        },
        {
            a: 2,
            b: 1
        }
    ]
}

Lets say you want to move { a: 1, b: 1 } to a different field, maybe called someOtherArrayField, you would want to do something like.

var doc = db.col.findOne({_id: "foo"});
var arrayDocToMove = doc.arrayField[0];
db.col.update({_id: "foo", arrayField: { $elemMatch: arrayDocToMove} }, { $pull: { arrayField: arrayDocToMove }, $addToSet: { someOtherArrayField: arrayDocToMove } })

The reason we use the $elemMatch is to be sure the field we are about to remove from the array hasn't changed since we first queried the document. When coupled with a $pull it also isn't strictly necessary, but I am typically overly cautious in these situations. If there is no parallelism in your application, and you only have one application instance, it isn't strictly necessary.

Now when we check the resulting document, we get:

db.col.findOne()
{
        "_id" : "foo",
        "arrayField" : [
                {
                        "a" : 2,
                        "b" : 1
                }
        ],
        "someOtherArrayField" : [
                {
                        "a" : 1,
                        "b" : 1
                }
        ]
}
Sign up to request clarification or add additional context in comments.

3 Comments

Can you provide an updated answer using 4.2's ability to specify a pipeline to an update command?
As of 4.2 you can do this in update in a single operation. It's better to update the answer here than asking a new question.
an updated 4.2 answer is here stackoverflow.com/a/57224243/431012 better would be to edit/add to this answer as the other question was a duplicate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.