MongoDB script creates an empty object when it finds a null object instead of ignoring it, how can I fix this? - mongodb

I have a large script, the following section is the relevant part, but if people prefer I can post the whole script on request.
The script iterates through a list, looking for a field called colour and renames it color.
{
serviceAgreementRefList: {
$map : {
input: "$$this.relatedJson.serviceAgreementRefList",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{
$ne: [
"$$this.situation",
undefined
]
},
"situation": {
$mergeObjects: [
"$$this.situation",
{
color: "$$this.situation.colour",
}
]
}
},
{},
]
}
]
}
}
}
}
It works as expected for the most part, however, if the object situation exists but is null then the script creates an empty situation object.
I would like any null situation objects to remain null.
How can I achieve that?
I thought I could add an $or function to the $cond, but of course that doesn't worse and in fact makes the problem worse.
Can I use an $in function, containing two $ne functions, one for undefined and one for null?
I would imagine I can, but I can't get the syntax right.

So the solution here is to use the $eq function instead of $ne and compare the type of the field to what we're expecting it to be.
If the type matches what we expect then the field name will be modified, if it doesn't, then nothing will be changed.
this is what the $cond function should look like:
$cond: [
{
$eq: [
{ $type: "$$this.situation" },
"object"
]
},
{ <code to execute if true> },
{ <code to execute if false> }
]
if situation is populated then $type will return "object", if it is null then $type will return "null" and if it does not exist $type will return "missing".

Related

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

Appending a string to all values in an array field in 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'
]
}
]
}
}
}
}
}
]
);

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 }
}
}
},
]
);

Delete matching two regular expressions on the same field

I need to delete documents but with multiple condition at the same variable.
db.getCollection('system_parameter').remove(
{
"variable":/^pickup_kota/,
"variable":
{
$nin:
[
/^pickup_kota_jakarta/
]
}
}
)
What I'm trying to do is, I want to delete all the data with the same prefix ('pickup_kota'), but excluding the ('pickup_kota_jakarta') documents.
If I execute the query above, ALL of the data is removed including let's say prefix 'some_doc' but excluding 'pickup_kota_jakarta'
All MongoDB query arguments are already AND conditions, so just include them on the same key:
db.getCollection('system_parameter').remove({
"variable": {
"$regex": /^pickup_kota/,
"$nin": [
/^pickup_kota_jakarta/
]
}
})
Or you can always write the "long form" with $and
db.getCollection('system_parameter').remove({
"$and": [
{ "variable": /^pickup_kota/ },
{ "variable": "$nin": [/^pickup_kota_jakarta/] }
]
})
So with two documents like this:
{ "variable" : "pickup_kota_somewhere" }
{ "variable" : "pickup_kota_jakarta" }
Only the first one gets removed
But as long as you can use a different operator such as $regex here to separate the conditions onto keys, then you don't need the full form.
Also since those are both anchored to the start of the string, it's more efficient for MongoDB to do the two comparisons than attempting a regular expression to meet both possible conditions. The only regular expression that could would break that bounding rule, and obviate the efficiency gained by searching anchored to the beginning of the string which can use an index.
You can try $all operator.
db.getCollection('system_parameter').remove({
"variable": {$all: [
{$regex: /^pickup_kota/},
{$nin:[/^pickup_kota_jakarta/]}
]}
})

Mongodb query - apply condition to subfield only when field exists

I need to find a document either when a field doesn't exist or when a subfield of this field meets some condition.
A similar question was asked here: Mongodb query - apply condition only if field exists. I'll use the code from this answer to illustrate:
db.stackoverflow.find({
$or: [
{ howmuch: { $exists:false } },
{ 'howmuch.chocolate':5 }
]})
Of course when I do that I get an error when howmuch is undefined. I know I could test if 'howmuch.chocolate' exists but that wouldn't change anything. Is there a way to do that?
This should work:
db.stackoverflow.find({
$or: [
{ howmuch: { $exists:false } },
{ $and: [{ 'howmuch.chocolate': { $exists: true } } ,{ 'howmuch.chocolate':5 }]}
]})
According to the documentation, if the first expression in the $and array evaluates to false, the remaining expressions are not evaluated.