Appending a string to all values in an array field in MongoDB - mongodb

I have a collection filled with docs that contain an "ip_addresses" field, which is an array of strings (IPs). I want to append '/32' to all of these values in all of my docs that don't already have a CIDR range suffix.
Here's my issue:
I don't know how to use the current value of the IP which is being iterated on.
Even if I did, $concat doesn't seem to work and throws an error even with placeholder values (as in the query below) - The dollar ($) prefixed field '$concat' in 'ip_addresses.0.$concat' is not valid for storage.
Here is my current query which throws the error:
db.getCollection('docs').update(
{},
{ $set: { "ip_addresses.$[ip]": { "$concat": [ "1.1.1.1", "/32" ] } } },
{
arrayFilters: [ { "ip": { $not: /.+\/\d{1,2}/ } } ],
multi: true
}
)
I'd appreciate help using the current values in the array in the $concat command and resolving the error.

Let's tackle each of the problems.
2 - $concat is an aggregation operator, hence it cannot be used for an update. you can view the list of available operators for an update here. You might notice that none of these "work" on actual document values which brings me back to the first question you had.
1 - In order you use a current document values within an update you have to use pipelined updates which is only available for Mongo v4.2+, if you're using a lesser Mongo version you have to fetch the documents into memory and do the update one by one in code. If you are on Mongo v4.2+ then the pipeline update syntax goes like this:
db.collection.updateMany(
{ip_addresses: {$exists: true}},
[
{
$set: {
ip_addresses: {
$map: {
input: '$ip_addresses',
as: 'ipAddress',
in: {
$cond: [
{
$regexMatch: {input: "$$ipAddress", regex: /.+\/\d{1,2}/}
},
'$$ipAddress',
{
$concat: [
'$$ipAddress',
'/32'
]
}
]
}
}
}
}
}
]
);

Related

Push an object to a nested array within a document but use a value from a field in the root document

I've been scratching my head with this problem. I've attempted to search for a solution but I didn't find anything relating to my specific use case.
Would anyone be able to help me out?
Say I have a collection of "discount" documents, and importantly they have an "amount" field to say how much the discount is worth. Whenever a discount is redeemed I currently want to track what the worth was at the time of the redemption.
To do this I've been attempting to use the following code:
await datastore.collection('discounts').updateOne(
{
$expr: { $gt: [ '$maxUses', '$uses' ] },
...criteria
},
{
$set: {
uses: 1
},
$push: {
redemptions: {
name: concatNames(user),
email: user.email,
amount: '$amount', // <-- use amount from root document
when: new Date()
}
}
}
)
Unfortunately $amount does not pull the value from the root document, instead it just becomes "$amount" as a string. I've also attempted to convert this update to use a pipeline but $push is not a valid pipeline stage.
Here's a quick Mongo playground link.
Thanks in advance.
In order to refer to another fields value, you'll need to use the aggregation pipeline form of update. However, '$push' is an update operator, not an aggregation operator.
$concatArrays gets most of the way there like
{$set: {redepmtions: {$concatArrays: [ "$redemptions", [{amount: "$amount"}]}}
That will throw an error if $redemptions doesn't already exist, so use $cond to subsitute an empty array in that case:
.updateOne(
{ ...criteria },
[{$set: {
redemptions: {$concatArrays: [
{$cond: [{$eq: ["array", {$type: "$redemptions"}]}, "$redemptions", []]},
[{amount: "$amount"}]
]}
}}]
)
Playground

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.

How can I update a property within an array of objects based on it's existing value in Mongo?

I have some documents with the following structure...
{
user: "Joe",
lists: [
{ listId: "1234", listName: "dogs" },
{ listId: "5678", listName: "cats" }
]
}
I am trying to prepend a string to each listId field but I am stuck. Amongst other things I have tried...
db.users.updateMany(
{"lists.listId": /^[0-9a-f]{20,}$/},
[{$set:
{"lists.listId.$[]": {"$concat": ["0000", "$lists.listId"]}}
}]
)
But I got the error message: "FieldPath field names may not start with '$'"
Variations on this write results into the appropriate field, but not the results I'm after.
I've bashed my head against the docs for a few hours now but all the references I can find to using the positional operator to reference the value of the field that is being updated use the field name directly, not referenced as a property like I am doing. I've not really messed with pipelines a lot before and I'm finding it all a bit confusing! Someone kindly helped me with a closely related problem yesterday, using $map, and that worked great for a plain array of strings but I haven't had any luck adapting that to an array of objects with string properties. Sorry if this is Mongo 101, the docs are good, but there's a lot of them and I'm not sure which bits are relevant to this.
You can do it like this:
db.collection.users({},
[
{
"$set": {
lists: {
$map: {
input: "$lists",
in: {
$mergeObjects: [
{
"listName": "$$this.listName",
"listId": {
$concat: [
"0000",
"$$this.listId"
]
}
}
]
}
}
}
}
}
],
{
"multi": true
})
Here is the working example: https://mongoplayground.net/p/Q8kUTB6X5JY

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.

Nested Mongodb Query needing multi seareshes

Hi I'm new with mongodb and I have to write a query to check if each field in database has a parent, if yes change the hasChild field to "true" else leave it be the default value of false.
Here is sample of one Document.
{
"_id":"5e19ef611052250ba51abb7b",
"name":"shampoo",
"price":12.99,
"hasChild":true
}
{
"_id":"5e1b268d25fe046b3518dcb9",
"name":"conditioner",
"price":"13.99",
"parent":"5e19ef611052250ba51abb7b",
"hasChild":false
}
I though a query which could ,while updating a filed search in other documents, check whether they have a parent id same as current documents id and set true/false would work out. but I couldn't find a way to write it. I would appreciate any ideas.
note that "_id" and "parent" are ObjectId.
You can do it with aggregation framework.
Something like this with $addFields and $cond:
db.getCollection("yourcollection").aggregate(
[
{
$addFields: {
"hasChild": {
$cond: { if: { $eq: [ "$parent", undefined ] }, then: false, else: true }
}
}
},
]
);