Update key in nested document - mongodb

I would like to update the keys of the object "values" in the following document stored in a MongoDB collection:
{
"data": {
"name": "Doe",
"values": {
"AA_Avg": 13,
"BB_Avg": 19,
"CC_Avg": 18
}
}
}
To make it looks like this:
{
"data": {
"name": "Doe",
"values": {
"AA_MT": 13,
"BB_MT": 19,
"CC_MT": 18
}
}
}

Dynamic update you can use update with aggregation pipeline starting from MongoDB 4.2,
First approach: using $rpelaceOne,
$objectToArray convert data.values from object to array in k and v format
$map to iterate loop of above converted array
$replaceOne will replace string on the base of input and replacement
$arrayToObject back to convert from array to object
db.collection.update({},
[{
$set: {
"data.values": {
$arrayToObject: {
$map: {
input: { $objectToArray: "$data.values" },
in: {
k: {
$replaceOne: {
input: "$$this.k",
find: "Avg",
replacement: "MT"
}
},
v: "$$this.v"
}
}
}
}
}
}
])
Playground
Second approach: using $split, $arrayElemAt, $concat,
Playground

You can use $rename function like this:
Is simply, you only have to say what field do you want to update and the new value.
Note that due to nested objects you have to use dot notation as explained into docs
MongoDB uses the dot notation to access the elements of an array and to access the fields of an embedded document.
db.collection.update({},
{
"$rename": {
"data.values.AA_Avg": "data.values.AA_MT",
"data.values.CC_Avg": "data.values.BB_MT",
"data.values.DD_Avg": "data.values.CC_MT"
}
})
Example here

Related

How can I $unset a sub-document that's part of another sub-document in MongoDB?

I have a collection containing documents of the following form:
{
tokens: {
name: {
value: "...",
},
...
}
}
name can be anything, and there can be multiple embedded documents assigned to different keys, all of which have a value field.
How do I $unset all embedded documents that have a certain value? { $unset: { "tokens.value": "test" } } doesn't work, and nor does "tokens.$[].value" or "tokens.$.value".
You can use pipelined form of update, like this:
db.collection.update({},
[
{
"$set": {
"tokens": {
$arrayToObject: {
"$filter": {
"input": {
"$objectToArray": "$$ROOT.tokens"
},
"as": "item",
"cond": {
"$ne": [
"$$item.v.value",
"test"
]
}
}
}
}
}
}
],
{
multi: true
})
Playground link.
In this query, we do the following:
We convert tokens object to an array, using $objectToArray.
We filter out the array elements which have a value key present in embedded documents and have a value of test using $filter.
Finally we convert the filtered array back again to object, and assign it to tokens key.

MongoDB - Update data type for the nested documents

I have this collection: (see the full collection here https://mongoplayground.net/p/_gH4Xq1Sk4g)
{
"_id": "P-00",
"nombre": "Woody",
"costo": [
{
"tipo": "Cap",
"detalle": "RAC",
"monto_un": "7900 ",
"unidades": "1",
"total": "7900 "
}
]
}
I tried a lot of ways to transform monto_un, unidades and total into int, but I always get an error.
Neither of these works.
db.proyectos.updateMany({}, {'$set': {"costo.monto_un": {'$toInt': 'costo.$.monto_un'}}})
db.collection.update({},
[
{
$set: {
costo: {
monto_un: {
$toInt: {
costo: "$monto_un"
}
}
}
}
}
],
{
multi: true
})
MongoDB 5.0.9
Any suggestions?
$set - Update costo array.
1.1. $map - Iterate each element in the costo array and return a new array.
1.2. $mergeObjects - Merge current document with the document from 1.3.
1.3. A document with the monto_un field. You need to trim space for the monto_un field in the current iterate document via $trim and next convert it to an integer via $toInt.
In case you are also required to convert the unidades and total as int, add those fields with the same operator/function logic as monto_un in 1.3. Those fields in the document (1.3) will override the existing value due to $mergeObjects behavior.
db.collection.update({},
[
{
$set: {
costo: {
$map: {
input: "$costo",
in: {
$mergeObjects: [
"$$this",
{
monto_un: {
$toInt: {
$trim: {
input: "$$this.monto_un"
}
}
}
}
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground

can't get data for unknown key in mongodb query

I want to get a subarray from a document using mongodb queries but I don't know the key for it. My data looks something like this:
{ 'log file name I dont know': [Array] }
'log file name I dont know' is the unique key at that level. Is there something like "get value for the first key" in mongodb aggregation pipeline?
1 Maybe something like this:
db.collection.aggregate([
{
$group: {
_id: "$_id",
new: {
$first: "$$ROOT"
}
}
},
{
"$project": {
"map": {
"$objectToArray": "$new"
}
}
},
{
$unwind: "$map"
},
{
$match: {
"map.k": {
"$ne": "_id"
},
"map.v": 4
}
}
])
Explained:
Add the root document to new key named "new"
Convert the object named "new" to array named "map"
$unwind the array
Remove the array elements with k:"_id" , so in the final result to have only:
map.k -> the unknown keys
map.v -> the array values for the unknown keys
Finally you can add in the $match stage the searched element , in the example:"map.v":4
playground1
2 Here is the option when you want to preserve the strange structure with the unknown key after the match stage:
db.collection.aggregate([
{
$group: {
_id: "$_id",
new: {
$first: "$$ROOT"
}
}
},
{
"$project": {
"map": {
"$objectToArray": "$new"
}
}
},
{
$match: {
"map.v": 1
}
},
{
"$project": {
"map": {
"$arrayToObject": "$map"
}
}
},
{
$replaceRoot: {
newRoot: "$map"
}
}
])
Explained:
Add the root document to new key named "new"
Convert the object named "new" to array named "map"
Match the necessary documents(in the example this is array element 1 ( "map.v":1 )
Convert back the array to object.
Replace the root tocument with the "map docunent" so it looks as original collection
playground2
Few words: File name value is not recommended to be a key , best practice is the filename to be key/value so if you want to search on filename value to be possible to create index on that filename key field , so your json structure to looke something like:
{ "filename":"The unknown file name" , "theArray":[1,2,3] }

Is there a way to give order field to the result of MongoDB aggregation?

Is there any way to give order or rankings to MongoDB aggregation results?
My result is:
{
"score":100
"name": "John"
},
{
"score":80
"name": "Jane"
},
{
"score":60
"name": "Lee"
}
My wanted result is:
{
"score":100
"name": "John",
"rank": 1
},
{
"score":80
"name": "Jane"
"rank": 2
},
{
"score":60
"name": "Lee"
"rank": 3
}
I know there is a operator called $includeArrayIndex but this only works with $unwind operator.
Is there any way to give rank without using $unwind?
Using $unwind requires grouping on my collection, and I'm afraid grouping pipeline would be too huge to process.
The other way is to use $map and add rank in document using its index, and don't use $unwind stage because it would be single field array you can directly access using its key name as mention in last line of code,
$group by null and make array of documents in root array,
$map to iterate loop of root array, get the index of current object from root array using $indexOfArray and increment that returned index number using $add because index start from 0, and that is how we are creating rank field, merge object with current element object and rank field using $mergeObjects
let result = await db.collection.aggregate([
{
$group: {
_id: null,
root: {
$push: "$$ROOT"
}
}
},
{
$project: {
_id: 0,
root: {
$map: {
input: "$root",
in: {
$mergeObjects: [
"$$this",
{
rank: { $add: [{ $indexOfArray: ["$root", "$$this"] }, 1] }
}
]
}
}
}
}
}
]);
// you can access result using root key
let finalResult = result[0]['root'];
Playground

Group 2 objects into 1 object and merge the keys

I am trying to group 2 objects in my MongoDB documents into a single object with the keys merged. What I have is:
{
"_id": ObjectId("..."),
"object_a": { "keyA": 1, "keyB": "valueB" }
"object_b": { "keyC": 2 }
}
And what I try to get is the following:
{
"_id": ObjectId("..."),
"object_a": { "keyA": 1, "keyB": "valueB", "keyC": 2 }
}
I can assert that there is no key conflict
I don't know the key names in advance
I tried with the $addFields operator but this will nest object_b inside object_a, not merge the keys.
{
"$addFields":
{
"object_a": "$object_b"
}
}
In other words, I am looking for a $setUnion but for objects.
You can use $mergeObjects in 3.6.
{"$addFields":{"object_a": {"$mergeObjects": ["$object_a", "$object_b"]}}}
For 3.4 you can use $arrayToObject and $objectToArray to merge keys.
{"$addFields":{"object_a": { "$arrayToObject": {"$setUnion": [{"$objectToArray": "$object_a"},{"$objectToArray": "$object_b"}]}}}}
Here is an improved version for Mongo 3.4.4+ that handles null values. It is based on the solution provided by #Veeram
function $mergeObjects(...items) {
return {
$arrayToObject: {
$setUnion: items.map(_ => (
{$filter: {input: {$objectToArray: _}, cond: {$ne: ["$$this.v", null]}}}
))
}
}
}