11

Is there a way to update a document array to move an item from one index to another? e.g.

{
   name: "myDoc",
   items: ["it1", "it2", "it3"]
}

As a result for JQuery-UI Sortable event it2 and it3 switched places. As a result I want to update their position in the myDoc which is stored in MongoDB.

Guy Korland
  • 9,139
  • 14
  • 59
  • 106
  • 2
    No, there's not a way to do that in a single step. http://docs.mongodb.org/manual/reference/operator/#operators. In fact, there's not even a way to insert an item into a specific index. It's an unresolved/open issue: https://jira.mongodb.org/browse/SERVER-2363. You'll need to rewrite the entire array list. – WiredPrairie Jun 12 '13 at 21:25
  • 1
    What do you think will be the best practice for that? – Guy Korland Jun 12 '13 at 22:03
  • UPDATE: as of version 2.6.you insert into a specific index using $position - https://docs.mongodb.org/manual/reference/operator/update/position/ – Lucidity Mar 17 '16 at 17:35
  • I added an issue for this: https://jira.mongodb.org/browse/SERVER-23239 . If you think its important, please leave a comment there. – B T Mar 18 '16 at 22:37

2 Answers2

8

Here is another way to move an element to a new position which will swap the positions of it2 and it3...

  1. Remove the element from the array using $pull [ Docs Here ].

    update({"name": "myDoc"}, {$pull: {"items" : "it3"}});
    
  2. Insert the element in the new position using $push. [ Docs Here ].

    update({"name": "myDoc"}, { 
        $push: { 
            "items" : { $each : [ "it3" ], $position : 1 }
        }
    });
    

When to Use

Pouzor's answer to use $set might be simpler and perform better for many use cases.

However, if multiple users are simultaneously adding, removing, and re-ordering array items this method means you won't overwrite each other's changes.

It might also be more efficient in some cases (e.g. large array elements) because less data is being written.

GOTCHA: List of Lists

If the list you are reordering is an array of arrays you need to use the $all operator with $pull [ Docs Here ]

Take this example:

{
    name: "myDoc",
    items: [  
        [ "User", "dofij20r91dj93" ],   
        [ "User", "239vjvidjfsldf" ], 
        [ "User", "2309jvdsjdkk23" ]
    ]
}

Here's the code to remove the first list from the list of lists:

update({"name": "myDoc"}, {
    $pull: {
        "items" : {
            $all : [ "User", "dofij20r91dj93" ]  // the sub-list to $pull
        }
    }
});

List of Objects

This is easy. Say you have the following list of objects:

{
    name: "myDoc",
    items: [  
        { type: "User",  id: "dofij20r91dj93", name: "Dave" },   
        { type: "Group", id: "239vjvidjfsldf", name: "Accountants" }, 
        { type: "User",  id: "2309jvdsjdkk23", name: "Toni" }
    ]
}

You can $pull like this:

update({"name": "myDoc"}, {
    $pull: { 
        "items" : { type: "User", id: "dofij20r91dj93" } 
    }
});
Lucidity
  • 1,299
  • 17
  • 19
  • 1
    Can you do the `$pull()` and `$push()` in the same `update()`? Do they run sequentially? ie if using a single `update()`, is it interpreted as: `when the pull is finished do the push`? – user1063287 Jul 04 '19 at 03:32
  • Re: my last question, according to this answer it is not: https://stackoverflow.com/a/34218493 But I think that is OK because I actually want to returned the pulled object from the first operation so that I have it as a value to push back into the array at the desired index. – user1063287 Jul 04 '19 at 04:24
  • One pitfall worth mentioning; if you do the `$pull` in one update, and the `$push` in a second update, then if your app crashes in between those updates there's a very real danger you'll 'lose' the item you were trying to re-order. – Jason Walton Aug 12 '19 at 19:07
  • @user1063287 I tried it and it doesn't let you push and pull in same update: https://mongoplayground.net/p/hcVwSSKDfBx – programmerRaj Jul 12 '22 at 14:40
5

For now, there is no way to do this directly in mongo with any driver.

You need to do this in your application and set the array with mongo.

ex:

var new_array = ["it2","it1","it3"];//Do your sort/place in js

$mongo_connexion->update({"name": "myDoc"}, {$set: {"items" : new_array}});
Pouzor
  • 103
  • 1
  • 9