MongoDB: Add and remove from array field at the same time - mongodb

I want to rename tags in our documents' tags array, e.g. change all tags a in the collection to c. The documents look something like this:
[ { _id: …, tags: ['a', 'b', 'c'] },
{ _id: …, tags: ['a', 'b'] },
{ _id: …, tags: ['b', 'c', 'd'] } ]
I need to keep tags unique. This means, an update like this will not work, because the first document will end up containing tag c twice:
db.docs.update(
{ tags: 'a' },
{ $set: { 'tags.$': 'c' } }
)
So, I tried this alternatively:
db.docs.update(
{ tags: 'a' },
{
$pull: { 'a' },
$addToSet: { 'c' }
}
)
But this gives a MongoError: Cannot update 'tags' and 'tags' at the same time.
Any chance of renaming the tags with one single update?

According to official MongoDB documentation, there is no way of expressing "replace" operation on a set of elements. So I guess, there isn't a way to do this in single update.
Update:
After some more investigation, I came across this document. If I understand it correctly, your query should look like this:
db.docs.update({
tags: 'a'
}, {
$set: { 'tags.$': 'c'}
})
Where 'tags.$' represents selector of the first element in "tags" array that matches the query, so it replaces first occurence of 'a' with 'c'. As I understand, your "tags" array does not contain duplicates, so first match will be the only match.

Related

Remove entire objects by array of ids

I have an array of objects that contains metadata and looks similar to this.
Data:
metadata:[
{ matchid: '1', region: 'europe' },
{ matchid: '2', region: 'africa' },
{ matchid: '3', region: 'asia' },
]
I have an endpoint setup to receive an array of IDS ['1', '2'] which would the remove all the objects containing these IDS.
This is my current query:
Query to remove objects
xx.findByIdAndUpdate(
id,
$pullAll: {
"metadata.matchid": {
$in: req.body.matches
}
}
)
I am expecting both objects with the ids of 1 and 2 to be removed
Expected Results:
metadata:[
{ matchid: '3', region: 'asia' },
]
I am recieving an error I have never seen before it is an object that says codeName: "BadValue"
As documentation says:
The $pullAll operator removes all instances of the specified values from an existing array. Unlike the $pull operator that removes elements by specifying a query.
$pullAll requires and exact match and $pull is like to use a filter. So you can use $pull in this way.
yourModel.findByIdAndUpdate(
id,
$pull: {
metadata:{
matchid: { $in: req.body.matches}
}
}
)
Example here

How to combine $in and $elemMatch properly with MongoDB (PyMongo)

In my database, I've got data stored according to the following scheme:
{'_id': ...,
'names': [{'first': ...,
'last': ...},
{'first': ...,
'last': ...},
...
],
...
}
Now, in my program, I get a list of names according to the following scheme:
name_list = [(first_name1, last_name1), (first_name2, last_name2), ...]
What I want, is to find all documents where any of these combinations of first/last name found in name_list are contained in the names array.
If I'd have just one name to check (instead of a list), I'd use the following query:
query = {'names':
{'$elemMatch':
{'first_name': first_name,
'last_name': last_name}
}}
So I could supply this query for every name in the list, doing something like:
all_results = []
for first_name, last_name in name_list:
rv := # Result from query
# combine all_results and rv
But I feel like there should be a better way to do this.
A multi-elem match query could be created from name_list using $or and $elemMatch which works in a way to find the documents where any of those first/last combination matches.
Query: considering name_list to be
//[(first_name1, last_name1), (first_name2, last_name2), (first_name3, last_name3)]
db.collection.find({
$or: [
{
names: {
$elemMatch: {
first: "first_name1",
last: "last_name1"
}
}
},
{
names: {
$elemMatch: {
first: "first_name2",
last: "last_name2"
}
}
},
{
names: {
$elemMatch: {
first: "first_name3",
last: "last_name3"
}
}
}
]
});

Mongodb: Get last item from array in document

I have a collection in MongoDB with elements looking like this
{
userId: 'X',
access: [
{ deviceId: 'a', time: "A timestamp" },
{ deviceId: 'b', time: "Another timestamp" },
]
}
I want to match documents based on userId and then I want to get the last element in the access array. The value I am most interested in here for user with id "X" is "Another timestamp".
I do not want mongodb to return the entire document, just that last element and always the last one.
How can I write a query/aggregation that solves this?
Try using $slice:
db.collection.find({ userId: 'value' }, { access: { $slice: -1 } } )

Remove items from Array which match at least one condition from a set of conditions

I'm trying to execute a query which remove some specific elements which match at least one condition from a set of conditions,
{
id: 'myId',
path2: [{
a: '1'
},{
a: '2'
},{
a: '3'
}]
}
and update it to:
{
id: 'myId',
path2: [{
a: '1'
}]
}
Here, I removed from path2 all elements where the value of the 'a' field is equal to either 2 or 3.
I tried the following with no success (I'm using mongoose):
let conditions = ['2', '3'];
myModel.findOneAndUpdate({id: 'myId'},
{$pull: {path2: {$elemMatch: {a: {$in: conditions}}}}}
);
Thank you in advance.
This should do,
myModel.findOneAndUpdate({id: 'myId'}, {$pull: {path2: {a: {$in: conditions}}}} );
You don't need to use elemMatch.

Update/Iterate over embedded Array MongoDB

I have a document structure like so:
{
data: null,
subArray: [{
key: 'a',
subData: []
}, {
key: 'b',
subData: []
}]
}
I will receive an array containing subArray keys, like: `['a', 'b']
I want to iterate over the subArray to push a value to subData, so that when subArray is ['a','b'], my data will now be like:
{
data: null,
subArray: [{
key: 'a',
subData: ['dataToPush']
}, {
key: 'b',
subData: ['dataToPush']
}]
}
I have this query to do that:
update({_id:"...", "subArray.key":{"$in":['a','b']},
{'$addToSet':{'subArray.$.subData':'...'}}})
Thinking that $in will let me loop through my subArray.
But I realize this does not work because $in gets the elements that match the elements in the array, so I will get my data but not iterate over my subArray.
Is it possible to complete this with a specific selector/modifier combo?
No, not with multiple keys. For one key "a", you could use a query like
db.test.update({ "subArray.key" : "a" }, { "$push" : { "subArray.$.subData" : "dataToPush" } })
but the $ positional operator can only be used for a single match in one update. You could do one update like the above per key, or retrieve the document, modify it based on all of the keys, then save over the original.