How to replace dynamic keys in mongodb? - mongodb

I have multiple documents of below structure.
"GENUS_DATA": {
"File_6489248.ZLM": {
"MeterID_6489248": {}
}
}
"GENUS_DATA": {
"File_12345.ZLM": {
"MeterID_12345": {}
}
}
I want to replace File_6489248.ZLM and File_12345.ZLM with name "File-Name" AS KEY
dynamically in mongo-Db remember there will be n number of records so defining only the above 2 values will not work.

One option is to use $objectToArray with update pipeline:
db.collection.update({},
[
{$set: {GENUS_DATA: {$objectToArray: "$GENUS_DATA"}}},
{$project: {GENUS_DATA: {"File-Name": {$first: "$GENUS_DATA.k"},
"data": {$first: "$GENUS_DATA.v"}}}
}
],
{multi: true})
See how it works on the playground example
EDIT:
If you consider each dictionary here can have several keys and you want all keys to be generic (as they should), you can use $map as each key can contain array of items:
db.collection.aggregate([
{$project: {GENUS_DATA: {$objectToArray: "$GENUS_DATA"}}},
{
$project: {
GENUS_DATA: {
$map: {
input: "$GENUS_DATA",
as: "item",
in: {"File-Name": "$$item.k", met: {$objectToArray: "$$item.v"}}
}
}
}
},
{
$project: {
GENUS_DATA: {
$map: {
input: "$GENUS_DATA",
as: "file",
in: {"File-Name": "$$file.File-Name",
met: {
$map: {
input: "$$file.met",
as: "met",
in: {"METNO": "$$met.k", data: "$$met.v"}
}
}
}
}
}
}
}
])
See how it works on the playground example - generic multi keys

Related

how to modify every field of a nested document mongo?

so imagine I have the following document:
{
"_id":...,
"data":{"a":[],"b":[],"x":[]}
}
I don't know beforehand which fields the subdocument data may have. I just know that every field in that subdocument will be an array
How do I make an update so that the object results like:
{
"_id":...,
"data":{"a":[1],"b":[1],"x":[1]}
}
Constraint: Using only mongodb operators. One single update. Without knowing the fields inside the 'data' subdocument
db.collection.update({},
[
{
$set: {
data: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$data" },
as: "d",
in: { k: "$$d.k", v: [ 1 ] }
}
}
}
}
}
])
mongoplayground

mongodb : find documents that contains the most a specific kind of property

I have a collection "MyCollection" where each document could have 0,1 or many properties named "propertyX" where X is the nth time the property appears in the document.
For example, my collection could looks like this :
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
}
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"property4":xxxxxx
"property5":xxxxxx
}
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"property4":xxxxxx
"property5":xxxxxx
"property6":xxxxxx
"property7":xxxxxx
}
I'm trying to sort documents with a mongo query by the number of propertyX they had, so in my example, the third object must be at the top of my result (7 properties), then the second one (5 properties), then the first one (only 3 properties). What I've done for the moment, is implementing it in javascript (using mongo driver) and do a count of keys with the Object.Keys(myObject).filter(key=>key.startsWith('property')) but this is taking way too much time. Is there a way to do it directly with a mongo query, thanks
You can achieve this by using the aggregation tool.
First create a new property with the number of properties in you document, so you can use it to sort on a later stage:
db.getCollection("MyCollection").aggregate([
{
$addFields: {
properties: { $objectToArray: '$$ROOT' },
}
}
]);
At this stage your documents on the pipeline would look something like this:
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"properties: [
{ k: "_id", v: ObjectId("XXXX")},
{ k: "property1", v: xxxxxx },
{ k: "property2", v: xxxxxx },
{ k: "property3", v: xxxxxx },
]
}
Using the $size operator you can get the length of this array:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
}
...
So now you would have a property that shows the number of keys in your document:
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"properties: 4,
}
Which would make it possible to sort the documents by properties number:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
},
{
$sort: { properties: -1 }
}
...
To finish you can get rid of the property properties with $unset:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
},
{
$sort: { properties: -1 }
},
{
$unset: 'properties'
},
You would end up with the following:
[
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"property4":xxxxxx,
"property5":xxxxxx,
"property6":xxxxxx,
"property7":xxxxxx,
},
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"property4":xxxxxx,
"property5":xxxxxx,
},
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
}
]

How to merge objects present in two different arrays into a single array of objects?

Below is an example structure of a document:
{
userName:"herobrewer555",
recentBrews:[{_id:1, name:'Watermelon wine', wortRating:'D6Z'}, {_id:2, name:'Pineapple wine', wortRating:'A5Z'}, {_id:3, name:'Banana mead', wortRating:'C6U'}],
brewDetails:[{_id:1, mainIngredient:'watermelon', sugarSource:"white sugar"}, {_id:2, mainIngredient:'pineapple', sugarSource:"white sugar"}, {_id:3, mainIngredient:'banana', sugarSource:"honey"}]
}
Is it possible to merge objects inside recentBrews and brewDetails based on their _id like below?
{
userName:"herobrewer555",
recentBrews:[{_id:1, mainIngredient:'watermelon', sugarSource:"white sugar", name:'Watermelon wine', wortRating:'D6Z'}, {_id:2, mainIngredient:'pineapple', sugarSource:"white sugar", name:'Pineapple wine', wortRating:'A5Z'},{_id:3, mainIngredient:'banana', sugarSource:"honey", name:'Banana mead', wortRating:'C6U'}]
}
In some cases there is an index correspondence between the two arrays, i.e, the first object in recentBrews and the first object in brewDetails will always refer the same brew.
$map to iterate loop of recentBrews array
$filter get matching object from brewDetails array
$arrayElemAt to get first element from above $filter result
$mergeObjects to merge current object and returned object from $arrayElemAt
db.collection.aggregate([
{
$project: {
userName: 1,
recentBrews: {
$map: {
input: "$recentBrews",
as: "r",
in: {
$mergeObjects: [
"$$r",
{
$arrayElemAt: [
{
$filter: {
input: "$brewDetails",
cond: { $eq: ["$$this._id", "$$r._id"] }
}
},
0
]
}
]
}
}
}
}
}
])
Playground

MongoDB - Array of object filter with condition

I have a json file that look like this in my collection :
[
{
"change":"00001",
"patchset":"4"
},
//etc
]
Two different object can have the same "change" properties.
So first I want to group them by "change" properties and inside this group I want the highest value of the "patchset" properties. I have managed to do this easily with this command
db.collections.aggregate([{$group:{_id:"$change",patchset_max:{$max:"$patchset"}}}])
but then, and this is where I lost it, with this max patchset, I want to get all the objects where object.patchset = max_patchset but still in the group array.
I tried with $filter and $match and then nested $group but nothing works,
Do you guys have any idea ?
Thanks
You need to use $$ROOT which is a special variable that represents whole document to get all the items for each group and then you can use $addFields to overwrite existing array and $filter to get only those docs that have patchset equal to patchset_max. Try:
db.collection.aggregate([
{
$group: {
_id: "$change",
patchset_max:{$max:"$patchset"},
docs: { $push: "$$ROOT" }
}
},
{
$addFields: {
docs: {
$filter: {
input: "$docs",
as: "doc",
cond: {
$eq: [ "$patchset_max", "$$doc.patchset" ]
}
}
}
}
}
])
Sample playground here
EDIT Answer above is correct, but if on Mongo 3.2, here's an alternative
db.collection.aggregate([{
$group: { _id: "$change", patchset: { $push: "$$ROOT" }, patchset_max:{ $max:"$patchset" } }
},{
$project: {
patchset: {
$filter: {
input: '$patchset',
as: 'ps',
cond: { $eq: ['$$ps.patchset', '$patchset_max'] }
}
}
}
}])

Renaming Field Within an Array in MongoDB

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.