i've a problem updating a nested object in MongoDB 4.
I wrote a playground where I'm doing some tests to be able to solve the problem ..
My desiderata is:
I need the "email" item to be overwritten with the lowercase of itself (result obtained).
I need that in all the N objects contained in the "emailsStatus" array the "emailAddress" item is overwritten with itself but in lowercase
I can not carry out the second point, in the playground you will find everything ready and with the test I am carrying out but I am not succeeding .. What am I wrong?
Playground: https://mongoplayground.net/p/tJ25souNlYZ
Try $map to iterate loop of emailsStatus array and convert emailAddress lower case, merge current object with update email field using $mergeObjects,
one more suggestion for query part is you can check single condition for email using $type is string
db.collection.update(
{ email: { $type: "string } },
[{
$set: {
email: { $toLower: "$email" },
emailsStatus: {
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{ emailAddress: { $toLower: "$$this.emailAddress" } }
]
}
}
}
}
}],
{ multi: true }
)
Playground
Check null condition in emailAddress array using $cond and $type,
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{
emailAddress: {
$cond: [
{ $eq: [{ $type: "$$this.emailAddress" }, "string"] },
{ $toLower: "$$this.emailAddress" },
"$$this.emailAddress"
]
}
}
]
}
}
Playground
Related
I'm writing an aggregation below. The purpose of the aggregation is to replace the value of targetedProperty, but only if targetedProperty already exists, targetedProperty being an optional property on objectToUpdate.
How would I adjust this code to do this?
{
$set: {
objectToUpdate: {
$mergeObjects: [
'$objectToUpdate',
{
targetedProperty: {
$cond: {
if: { $lte: ['$objectToUpdate.targetProperty', null] },
then: undefined,
else: 'newValue'
}
},
},
],
},
},
}
This is an example of an input:
{ otherProperty: 'value', anotherProperty: 'anotherValue' }
This is my expected result:
{ otherProperty: 'value', anotherProperty: 'anotherValue' }
This is my actual result:
{ otherProperty: 'value', anotherProperty: 'anotherValue', targetedProperty: null }
Note: I do have to do this as an aggregation because I am making use of additional aggregation operators in parts of the logic not shown here.
You need to change the order of your $cond, we first check if the field "targetedProperty" exists, if it doesn't we'll put the empty object {} for the $mergeObjects operator, meaning we won't update the object at all, If the field does exists then we'll just put the relevant value, like so:
db.collection.aggregate({
$set: {
objectToUpdate: {
$mergeObjects: [
"$objectToUpdate",
{
$cond: [
{
$eq: [
"$objectToUpdate.targetedProperty",
undefined
]
},
{},
{
targetedProperty: 123
}
]
}
]
}
}
})
Mongo Playground
As the title says, the field I need to modify is nested like this:
basicData.owners.relatedJson.basicData.devices.equipmentID
which owners and devices are both lists.
The object looks like this:
{
"basicData": {
"owners": [
{
"relatedJson": {
"basicData": {
"devices": [
{
"equipmentID": "abcd",
"type": "camera"
}
],
"otherFieldsBasicData": "other values",
"evenMoreFieldsBasicData": "other values"
},
"otherFieldsRelatedJson": "other values",
"evenMoreFieldsRelatedJson": "other values"
}
}
]
}
}
I want to rename equipmentID to equipmentId.
I've also asked this question, and I've been trying to create a query using that as a starting point, but with no success.
I was able to build a query that could get as far down as the devices list, but. then I wanted to call $set on that list, and I get an error because you can't call set inside $mergeObjects.
I thought there was some way I could use $[] to help iterate through the first array, but I can't get it to work. I think this approach is wrong.
This is what I've ended up with, which doesn't work:
db.myCollection.updateMany({"basicData.owners.relatedJson.basicData.devices.equipmentID": {$exists: true}},
[
{
$set: {
"basicData.owners$[].relatedJson.basicData.devices": {
$map: {
input: "$basicData.owners$[].relatedJson.basicData.devices", in: {
$mergeObjects: ["$$this",
{equipmentId: "$$this.equipmentID",}]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Any advice would be greatly appreciated.
Think you need two $map (with nested) operators.
First $map to iterate basicData.owners array while another $map is used to iterate relatedJson.basicData.devices array.
db.collection.updateMany({
"basicData.owners.relatedJson.basicData.devices.equipmentID": {
$exists: true
}
},
[
{
$set: {
"basicData.owners": {
$map: {
input: "$basicData.owners",
in: {
$mergeObjects: [
"$$this",
{
"relatedJson": {
$mergeObjects: [
"$$this.relatedJson",
{
"basicData": {
$mergeObjects: [
"$$this.relatedJson.basicData",
{
"devices": {
$map: {
input: "$$this.relatedJson.basicData.devices",
in: {
$mergeObjects: [
"$$this",
{
equipmentId: "$$this.equipmentID",
}
]
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Demo # Mongo Playground
I have several documents as given below. Now I need to do rename the middlename field into mid_name if middlename exists in the document.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"middilename":"xyz"
}
]
}
This is something that I expect it to be.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"mid_name":"xyz"
}
]
}
And this is what I have done but it throws the error.
db.md_carrierInformation.updateMany({"name.middlename":{$exists:true}}, {$rename:{"name.$.middlename":"name.mid_name"}})
ERROR
MongoServerError: The source field for $rename may not be dynamic: name.$.middlename
Work on the update with the aggregation pipeline.
$set - Set name array field.
1.1. $map - Iterate each item in name array and return new array.
1.2. $cond - Condition for checking current document's middlename is not existed.
1.2.1. If true, with merge current document with the document with field mid_name via $mergeObjects.
1.2.2. If false, remain the existing document.
$unset - Remove field for name.middlename.
db.md_carrierInformation.updateMany({
"name.middlename": {
$exists: true
}
},
[
{
$set: {
"name": {
$map: {
input: "$name",
in: {
$cond: {
if: {
$ne: [
"$$this.middlename",
undefined
]
},
then: {
$mergeObjects: [
"$$this",
{
mid_name: "$$this.middlename"
}
]
},
else: "$$this"
}
}
}
}
}
},
{
$unset: "name.middlename"
}
])
Sample Mongo Playground
Sample Doc :
{
bioupdate: [
{
date: "02/03/2020",
ts: "1583133621197-02/03/2020_15:20:21",
status: "1"
},
{
date: "02/03/2020",
ts: "1583135570542-02/03/2020_15:52:50",
status: "1"
},
{
date: "02/03/2020",
ts: "1583135586272-02/03/2020_15:53:06",
status: "0"
},
{
date: "21-03-2020:17:35:08",
ts: 1584783308231,
status: "1"
}
]
}
Below is the code I've tried with aggregation pipeline splitting the string with first '-' and take the first element which is epoch timestamp and save it to the same field to an existing array.
db.novelmodel.aggregate([
{$match: {pin: "JAIN"}},
{
$project: {
pin: 1,
bioupdate: {
$filter: {
input: "$bioupdate",
as: "bioupdateArray",
cond: { $and: [
{$arrayElemAt:[{$split:["$$bioupdateArray.ts", "-"]}, 0]}
] }
}
}
}
},
{$out:"novelmodel"}
]);
It gives me an error message: "errmsg" : "$split requires an expression that evaluates to a string as a first argument, found: double".I'm not sure how filter to take only the date which has delimiter '-' in a string
Your issue should be the last document which has ts as type NumberLong() instead of string, which is what throwing an error, Try below query :
db.collection.aggregate([
/** Re-create 'bioupdate' with updated data */
{
$addFields: {
bioupdate: {
$map: {
input: "$bioupdate", // Iterate on 'bioupdate' array
in: {
$cond: [
{ $eq: [{ $type: "$$this.ts" }, "string"] }, // Check if 'ts' field in current object is of type string
/** If YES, Split it & get first element from array, it will be string if you want to keep is as number convert using '$long' */
{
$mergeObjects: [
"$$this",
{
ts: {
$toLong: {
$arrayElemAt: [{ $split: ["$$this.ts", "-"] }, 0]
}
}
}
]
},
/** If NO, Just to make sure convert existing to long & merge with actual object & push entire object back to array */
{ $mergeObjects: ["$$this", { ts: { $toLong: "$$this.ts" } }] }
]
}
}
}
}
}
]);
Test : MongoDB-Playground
I need to update the name of field in a collection. The problem is that the field in question is within an array. So I'm trying to determine the correct way do this. I tried this to accomplish renaming the field that exists within the "plans" array: :
db.customers.updateMany( {}, { $rename: { "plans" : { "subscriptionType": "membershipType" } } } );
But this won't work. What's the correct way to handle this kind of transformation of a field within an array?
The data looks like this:
{
_id: 123,
prop1: value,
prop2: value,
prop3: value,
plans: [
subscriptionType: value,
otherProp: value,
otherProp: value
]
}
You can use Aggregation Framework's $addFields to override plans field and $map operator to rename field inside an array. Then you can use $out to override existing collection:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
membershipType: "$$plan.subscriptionType",
otherField: "$$plan.otherField",
otherField2: "$$plan.otherField2"
}
}
}
}
},
{
$out: "customers"
}
])
Alternatively you can do that dynamically. In this solution you don't have to explicitly specify other field names:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
$mergeObjects: [
{ membershipType: "$$plan.subscriptionType" },
{
$arrayToObject: {
$filter: {
input: { $objectToArray: "$$plan" },
as: "plan",
cond: { $ne: [ "$$plan.k", "subscriptionType" ] }
}
}
}
]
}
}
}
}
},
{
$out: "customers"
}
])
Using $objectToArray to $filter out old key-value pair and the using $mergeObjects to combine that filtered object with new renamed field.