Cannot use aggregation operations in $set inside updateMany - mongodb

I have a mongoDB (4.4.8) collection where I want to change the value of some field based on its previous value. For example, I want to convert all strings to uppercase.
For this, I use the following query:
db.collection.updateMany(
{ field: { $regex: "[a-z]+"}},
{ $set: { field: { $toUpper: "$field" } } }
)
when executing the query, it gives me the following error:
MongoError: The dollar ($) prefixed field '$toUpper' in 'field.$toUpper' is not valid for storage
The same occurs if I use similar operations such as $concat (with an array parameter) to append something to the field.
When I look up similar questions, it all uses update and tells me to use updateMany instead, or it says that it only works in mongoDB >= 4.2. However, I have both of these things.
If I am correct, you are able to use aggregation syntax (among which $toUpper) in conjunction with $set inside updateMany queries for these newer versions of mongoDB.
Does anyone know what I'm doing wrong here?

As in the comments of J.F. and turivishal, I managed to solve this by changing it into the following:
db.collection.updateMany(
{ field: { $regex: "[a-z]+"}},
[ { $set: { field: { $toUpper: "$field" } } } ]
)

Related

MongoDB aggregate $match using dynamic field path

I want to match documents in my pipeline based on whether the field to match is contained within an array that is within my documents.
Example document to match:
{
'wishlist': ['123','456','789'],
'productId': '123'
}
Example match aggregation:
{
$match: {
'productId': {$in: '$wishlist'}
}
}
This isn't working - error is '$in needs an array' - but '$wishlist' is an array? so clearly the stage isn't picking up the path reference.
How would I get something like this to work?
Thanks!
If you want to match the internal field of the document, you can use $expr expression operator, and I see that field has an array value then you have to use $in operator's aggregation syntax,
{
$match: {
$expr: {
$in: ["$productId", '$wishlist']
}
}
}

MongoDB update - set field from nested field

I want to set a field to a value of a nested field
given
{
"_id":"myId",
"data":{
"id":"asdfasdfasdf",
"text":"Wonderful text"
}
}
expected
{
"_id":"myId",
"messageId":"asdfasdfasdf",
"data":{
"id":"asdfasdfasdf",
"text":"Wonderful text"
}
}
Is there a possibility to do something like this?
db.myCollection.updateMany({},{ $set: {"messageId": "$data.id"} },false,true)
I am using MongoCompass -> _MongoSH
Yes, it's possible doing this using pipelined updates, added at version 4.2.
This is how you use it:
db.collection.updateMany(
{},
[
{
$set: {
"message": "$data.id"
}
}
])
Mongo Playground
For lesser Mongo versions you have to read the document first and then use the values for the update.

MongoDB $elemMatch comparison to field in same document

I'm wanting to create an aggregation step to match documents where the value of a field in a document exists within an array in the same document.
In a very worked example (note this is very simplified; this will be fitting into a larger existing pipeline), given documents:
{
"_id":{"$oid":"61a9085af9733d0274c41990"},
"myArray":[
{"$oid":"61a9085af9733d0274c41991"},
{"$oid":"61a9085af9733d0274c41992"},
{"$oid":"61a9085af9733d0274c41993"}
],
"myField":{"$oid":"61a9085af9733d0274c41991"} // < In 'myArray' collection
}
and
{
"_id":{"$oid":"61a9085af9733d0274c41990"},
"myArray":[
{"$oid":"61a9085af9733d0274c41991"},
{"$oid":"61a9085af9733d0274c41992"},
{"$oid":"61a9085af9733d0274c41993"}
],
"myField":{"$oid":"61a9085af9733d0274c41994"} // < Not in 'myArray' collection
}
I want to match the first one because the value of myField exists in the collection, but not the second document.
It feels like this should be a really simple $elemMatch operation with an $eq operator, but I can't make it work and every example I've found uses literals. What I've got currently is below, and I've tried with various combinations of quotes and dollar signs round myField.
[{
$match: {
myArray: {
$elemMatch: {
$eq: '$this.myField'
}
}
}
}]
Am I doing something very obviously wrong? Is it not possible to use the value of a field in the same document with an $eq?
Hoping that someone can come along and point out where I'm being stupid :)
Thanks
You can simply do a $in in an aggregation pipeline.
db.collection.aggregate([
{
"$match": {
$expr: {
"$in": [
"$myField",
"$myArray"
]
}
}
}
])
Here is the Mongo playground for your reference.

Mongodb: concat to existing document

I have my collection like this:
{
"_id" : "ID1234",
"read_object" : "sss-ssss",
"expireAt" : ISODate("2020-04-30T22:00:00.000Z")
}
In case he encounters the same ID, I would like to update the read_object field, otherwise create a new document.
I tried to do it like this:
db.collection.update(
{ _id: "ID1234" },
{
$set: { read_object: { $concat: ["$read_object", "test"] } },
},
{ upsert: true }
)
but I get an error every time:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
If I add square brackets before $set, like this:
db.collection.update(
{ _id: "1b1b871493-14a0-4d21-bd74-086442df953c-2020-02" },
[{
$set: { read_object: { $concat: ["$read_object", "test"] } },
}],
{ upsert: true }
)
I get this error:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
Where do I have a mistake?
$concat is an aggregation operator, meaning you can't use it while using the basic update syntax as you can only use update operators on it.
With that said Mongo version 4.2 introduces pipeline updates, which is basically what you're trying to do with the square brackets.
Assuming you are using Mongo version 4.2 heres a working example:
db.test1.update({_id: "ID1234"}, [
{$set: {"read_object": {$concat: [{$ifNull: ["$read_object", ""]}, "test"]}}}
], {upsert: true});
Basically we just need to "replace" read_object if document does not exist as it is undefined in that case.
If you are using Mongo version that's smaller than 4.2 then unfortunately there is no way to do what you want in one operation, you'll have to first read the document and then adjust accordingly.

mongodb, how to use $not filter in query

I have what should be a fairly simple query I'm trying to run in mongodb compass:
{ $and: [ { Source: "hostname" }, { Message: { $not: /.*unexpected data.*1234.*/ } } ] }
Basically, my document model contains a Source field and a Message field.
I need to query for all documents where the Source equals a given "hostname" and the Message does not contain "unexpected data...1234"
Everything works fine when I DO NOT include the $not filter on the regular expression... so I get all the documents where this message is contained... but now I need all the other messages where this is NOT contained... and I can't figure out how to use $not properly.
The example given in the MongoDb manual only shows using $not with one statement... but even this doesn't work for me for some reason...
{ Message: { $not: /.*unexpected data.*1234.*/ } }
Again, it works fine without the $not... what am I missing?
Edit:
Here is an image of what I'm talking about, placing this filter in MongoDb Compass, it indicates that the filter is incorrect... Is MongoDb Compass for some reason incapable of running complex filters?
Please try with $regex which should work:
find( { $and: [ { Source: "hostname" }, { Message: { $not: { $regex: /.*unexpected data.*1234.*/ } } } ] })
MongoDB Compass only supports regex surrounded by single quotes, not forward slashes. So $regex: /.*unexpected data.*1234.*/ should be $regex: '.*unexpected data.*1234.*'.
https://jira.mongodb.org/browse/COMPASS-2993