Mongodb: Get value from $[<identifier>] in update - mongodb

I want to rename a field inside a object itself inside a nested array.
As example, I want to rename the all tags m2 to m6 in this document:
{
"_id": 1,
"tagsGroup": [
{
"id": "1234",
"tags": {
"m1": 1,
"m2": 2
}
},
{
"id": "456",
"tags": {
"m3": 1,
"m2": 2
}
},
{
"id": "1234",
"tags": {
"m4": 2,
"m5": 2
}
},
]
}
This is my current state of work:
db.collection.update({},
{
"$set": {"tagsGroup.$[tGp].tags.m6": "$tagsGroup.$[tGp].tags.m2"},
"$unset": {"tagsGroup.$[tGp].tags.m2": ""}
},
{
arrayFilters: [{"tGp.tags.m2": {$exists: 1}}],
multi: true}
)
Unfortunately, the $tagsGroup.$[tGp].tags.m6 is not interpreted.
Do you guys, have a way to do this?
Thanks!

Probably similar to this question MongoDB rename database field within array,
There is no straight way to rename fields within arrays with a single command. You can try update with aggregation pipeline starting from MongoDB v4.2,
$map to iterate loop of tagsGroup array
$map to iterate loop of tags object after converting to array using $objectToArray, it will return in k and v format
$replaceOne will replace specific string on find field, this is starting from MongoDB v4.4
$arrayToObject convert tags array returned by second $map back to object format
$mergeObjects to merge current object with updated tags object
db.collection.update(
{ "tagsGroup.tags.m2": { $exists: true } },
[{
$set: {
tagsGroup: {
$map: {
input: "$tagsGroup",
in: {
$mergeObjects: [
"$$this",
{
tags: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$$this.tags" },
in: {
k: {
$replaceOne: {
input: "$$this.k",
find: "m2",
replacement: "m6"
}
},
v: "$$this.v"
}
}
}
}
}
]
}
}
}
}
}],
{ multi: true }
)
Playground

Related

MongoDB - How to match the value of a field with nested document field value

I have a structure where I want to match the value of a field on root level with the value of a field inside another object in the same document, I got to his structure by unwinding on the nested field. So I have a structure like this:
{
"name": "somename",
"level": "123",
"nested":[
{
"somefield": "test",
"file": {
level:"123"
}
},
{
"somefield": "test2",
"file": {
level:"124"
}
}
]
}
After unwinding I got the structure like:
{
"name": "somename",
"level": "123",
"nested": {
"somefield": "test",
"file": {
level:"123"
}
}
}
So I want to match on level = nested.file.level and return only documents which satisfy this condition.
I tried using
$match: {
"nested.file.level": '$level'
}
also
$project: {
nested: {
$cond: [{
$eq: [
'nested.file.level',
'$level'
]
},
'$nested',
null
]
}
}
Nothing seems to work. Any idea on how I can match based on the mentioned criteria?
Solution 1: With $unwind stage
After $unwind stage, in the $match stage you need to use the $expr operator.
{
$match: {
$expr: {
$eq: [
"$nested.file.level",
"$level"
]
}
}
}
Demo Solution 1 # Mongo Playground
Solution 2: Without $unwind stage
Without $unwind stage, you may work with $filter operator.
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"$level",
"$nested.file.level"
]
}
}
},
{
$project: {
nested: {
$filter: {
input: "$nested",
cond: {
$eq: [
"$$this.file.level",
"$level"
]
}
}
}
}
}
])
Demo Solution 2 # Mongo Playground

Mongodb having problem of adding two values inside nested document with dynamic key

I wish to add currentAsset.total and longTermAsset.total for each of my child documents with dynamic key to a new field. My current mongodb version is 4.0.12
My source document is as below:
{
"_id":"5f44bc4c36ac3e2c8c6db4bd",
"counter":"Apple",
"balancesheet":{
"0":{
"currentAsset":{
"total":123.12
},
"longTermAsset":{
"total":10.16
}
},
"1":{
"currentAsset":{
"total":10.23
},
"longTermAsset":{
"total":36.28
}
}
}
}
The result document I wanted to get is:
{
"_id": "5f44bc4c36ac3e2c8c6db4bd",
"counter": "Apple",
"balancesheet": {
"0": {
"currentAsset": {
"total": 123.12
},
"longTermAsset": {
"total": 10.16
},
"totalAsset": 133.28
},
"1": {
"currentAsset": {
"total": 10.23
},
"longTermAsset": {
"total": 36.28
},
"totalAsset": 46.51
}
}
}
I have tried a few aggegrates but failed as it is giving me "errmsg" : "$add only supports numeric or date types, not array"
db.balancesheets.aggregate([
{
$match: { counter: "Apple" }
},
{
$project: {
bs: { $objectToArray: "$balancesheet" }
}
},
{
$addFields: {
totalAsset: {
$add: ["$bs.k.currentAsset.total", "$bs.k.longTermAsset.total"]
}
}
}
])
As I refer to this, it seems like the version needs to be 4.2 and above. Is there anyway that will be able to do it on my existing 4.0.12 version?
MongoDB Aggregation: add field from an embedded document via a dynamic field path
There is no version issues, follow few fixes,
first 2 pipelines looks good,
$unwind deconstruct bs array
$addFields corrected, you used k instead of v in accessing field total
$group to reconstruct and prepare again object to array
$addFields to convert bs array to object using $reduce
db.collection.aggregate([
// $match ... pipeline
// $project ... pipeline
// unwind bs array
{ $unwind: "$bs" },
{
$addFields: {
"bs.v.totalAsset": { $add: ["$bs.v.currentAsset.total", "$bs.v.longTermAsset.total"] }
}
},
{
$group: {
_id: "$_id",
bs: { $push: { $arrayToObject: [["$bs"]] } },
counter: { $first: "$counter" },
},
}
{
$addFields: {
bs: {
$reduce: {
input: "$bs",
initialValue: {},
in: { $mergeObjects: ["$$value", "$$this"] }
}
}
}
}
])
Playground

MongoDB: Project only fields of a specific type

Is there a way in MongoDB to project all fields of a document that have a specific type?
For example if I have the following document:
{
_id: 5dde4c55c6c36b3bb4f5ad30,
name: "Peter",
age: 45,
division: "marketing"
}
I would like to say: Return only the fields of type string. This way I would end up with:
{
_id: 5dde4c55c6c36b3bb4f5ad30,
name: "Peter",
division: "marketing"
}
You can use $type to check the type of field,
$reduce input $$ROOT object as array using $objectToArray
check condition if field value type is string then concat with initialValue and return
return value will be array we need to convert it to array using $arrayToObject
$replaceWith will replace root to new returned object
db.collection.aggregate([
{
$replaceWith: {
$arrayToObject: {
$reduce: {
input: { $objectToArray: "$$ROOT" },
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$cond: [
{ $eq: [{ $type: "$$this.v" }, "string"] },
["$$this"],
[]
]
}
]
}
}
}
}
}
])
Playground

Match in an array within the current document

In an aggregation pipeline operating on documents like
{
"availablePackages": [
{
"title": "Silver",
"code": "001",
},
{
"title": "Gold",
"code": "002",
},
{
"title": "Platinum",
"code": "003",
},
"selectedPackageCode": "002"
}
I need to replace everything in the above document with the title of the package whose code matches the selectedPackageCode. So I want to the pipeline to end up with
{
"packageTitle": "Gold"
}
This is not a lookup, because it's in the current document. I thought I might be able to use $let to create a variable and then a $match to find the right array element, but I have not found a syntax that works.
You need $filter to match availablePackages with selectedPackageCode and $arrayElemAt to get first matching element. In order to make it in one aggregation stage you can use $let to define temporary variable:
db.col.aggregate([
// ... other stages
{
$project: {
packageTitle: {
$let: {
vars: {
selectedPackage: {
$arrayElemAt: [
{ $filter: { input: "$availablePackages", cond: { $eq: [ "$$this.code", "$selectedPackageCode" ] } } }, 0
]
}
},
in: "$$selectedPackage.title"
}
}
}
}
])

MongoDB aggregation: Aggregate array into keyed objects

I'd like to flatten document array into a keyed objects using the aggregation functionality. Here’s an example of my documents:
[
{
"_id": 1,
"texts": [
{ "language": "english", "text": "hello" },
{ "language": "german", "text": "hallo" },
{ "language": "french", "text": "bonjour" }
]
}, …
]
Expected result:
[
{
"_id": 1,
"texts": {
"english": "hello",
"german": "hallo",
"french": "bonjour"
}
}, …
]
I’ve looked at different operators, e.g. $map, but this seems to be focued on transforming array to array. I’d probably need a JS equivalent of $reduce, but couldn’t find a way of accumulating my values into an object.
Any ideas?
You need $map and $arrayToObject which converts an array of k-v pairs into single object:
db.col.aggregate([
{
$addFields: {
texts: {
$arrayToObject: {
$map: {
input: "$texts",
as: "text",
in: {
k: "$$text.language",
v: "$$text.text"
}
}
}
}
}
}
])
You can also use $zip with $arrayToObject.
Something like
db.col.aggregate([{
"$project": {
"texts":{
"$arrayToObject":{
"$zip": {
"inputs": [
"$texts.language",
"$texts.text"
]
}
}
}}
}])
You can try this using $map and $arrayToObject aggregation in mongodb 3.4.4 and above
Just convert language field with key(k) and text with value(v) using $map and transform the array to object using $arrayToObject
db.collection.aggregate([
{ "$addFields": {
"texts": {
"$map": {
"input": "$texts",
"in": {
"k": "$$this.language",
"v": "$$this.text"
}
}
}
}},
{ "$addFields": {
"texts": { "$arrayToObject": "$texts" }
}}
])