Reputation: 45941
In MongoDB, what is a good way to prepend an item to an array, and remove any duplicate items?
$addToSet
does not do anything if the item is already in the array.
This SO question shows two ways of prepending, but does not remove dupes.
Working in Mongo 2.4.1, MongoMapper and Ruby.
Upvotes: 1
Views: 428
Reputation: 151122
The $addToSet
operator will not of course remove existing duplicates, nor as being a "set" are any of the elements considered to be ordered so there is no way to position them with this operator.
In either case, looking after duplicates by your logic is now up to you to manage. But rather than pulling the document and manipulating in code, which could cause you concurrency problems, the bulk operations API can help you.
So in order to ensure your logic you would use $pull
to remove any items from the array that you were inserting, followed by a $push
, using the $position
modifier that needs to be coupled with the $each
modifier.
A basic mongomapper code example:
require 'mongo_mapper'
require 'pp'
MongoMapper.database = 'test'
class User
include MongoMapper::Document
key :array, Array
end
User.collection.remove
user = User.create(:array => [5, 4, 4, 6])
pp user
bulk = User.collection.initialize_unordered_bulk_op
bulk.find(:_id => user._id, :array => 4)
.update_one({ "$pull" => { "array" => 4 }})
bulk.find({"_id" => user._id, "array" => { "$ne" => 4 }})
.update_one({
"$push" => {
"array" => { "$each" => [4], "$position" => 0 }
}
})
res = bulk.execute()
pp res
pp user.reload
Strictly speaking, that is still not an atomic operation and is performed in multiple updates. But the API implementation does send both of these requests over the wire together as well as implements them in succession. So this is as close to an atomic operation as you are currently going to get given your logic.
The resulting array in this example is of course:
array: [4, 5, 6]
Upvotes: 1
Reputation: 312055
You can do this by qualifying your update
conditions to include that the value to prepend isn't already in the array. Then by using $push
with $each
and $position
you can insert the item at the beginning of the array, only if it's not already present in the array.
Assuming a doc that looks like:
{
"_id": 1,
"a": [1]
}
You can perform the update in the shell as:
var value = 1;
db.test.update(
{_id: 1, a: {$ne: value}},
{$push: {a: {$each: [value], $position: 0}}})
Which for a value
of 1 has no effect, but any other value would be prepended to the a
array field.
Upvotes: 1