How do I Convert a subset of fields in all documents to $toDouble? - mongodb

I have a database with many documents structured like so:
Date:2021-01-02T08:00:00.000+00:00
FIT_ME44_Volume:"984385"
LT_MJ01:"0"
LT_MJ08:"0"
LT_MJ16:"-34.526024"
FIT_ME56_Volume:"0"
FIT_MJ22_Volume:"9538598"
LT_MJ26:"-61.803848"
FIT_ME52_Volume:"2734271"
LT_ME16:"0"
FIT_MJ28_Volume:"0"
LT_ME29:"2.10552"
LT_ME02:"2.005206"
LT_ME50:"8.732683"
FIT_MJ13_Volume:"0"
FIT_ME02_Volume:"1131376"
FIT_ME23_Volume:"2585415"
LT_ME03:"6.918576"
FIT_MJ08_Volume:"0"
FIT_MJ18_Volume:"0"
QQCr_Total_Plant:"5471052"
FIT_ME03_Volume:"103164"
FIT_ME51_Volume:"3587575"
LT_ME06:"24.423439"
FIT_ME46_Volume:"1619"
What I would like to do is convert all fields in all documents except Date from string to double. I can do this on a field by field basis, but how do I accomplish this in bulk?

You can try an update with aggregation pipeline query starting from MongoDB 4.2,
$objectToArray convert root document from object to key-value array of object format
$map to iterate loop of above converted array
return k key as it is
$cond to check is key in the provided array then don't convert means ignore and return existing value, you can also add fields name if don't want to convert
else $toDouble to convert a string value to double
$arrayToObject back to convert above key-value array of objects to an real object format
$replaceRoot to replace above converted object to root
db.collection.updateMany(
{},
[{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$$ROOT" },
in: {
k: "$$this.k",
v: {
$cond: [
{ $in: ["$$this.k", ["Date", "_id"]] },
"$$this.v",
{ $toDouble: "$$this.v" }
]
}
}
}
}
}
}
}]
)
Playground

Related

MongoDB extract value from array of objects into simple array

I have a collection of documents that I initially set up like this:
{
_id: "some_id",
data: [
{
_id: "_id_foo",
value: "foo"
},
{
_id: "_id_bar",
value: "bar"
}
]
}
Now, I realized I don't need the value field, so I can turn the array of objects into an array of strings ["_id_foo", "_id_bar"]. I'm trying to build a pipeline to accomplish this, but I'm failing miserably.
If it's easier, I can also create a data_new array and delete the old one later.
Thanks!
You can use the map operator and convert the array of objects to array of strings.
db.collection.aggregate([
{
$addFields: {
data: {
$map: {
input: "$data",
as: "data",
in: "$$data._id"
}
}
}
}
])
Playground

How to sort a dictionary keys and pick the first in MongoDb?

I'm running the following query as described in the docs.
db.getCollection('things')
.find(
{ _id: UUID("...") },
{ _id: 0, history: 1 }
)
It produces a single element that, when unfolded in the GUI, shows the dictonary history. When I unfold that, I get to see the contents: bunch of keys and correlated values.
Now, I'd like to sort the keys alphabetically and pick n first ones. Please note that it's not an array but a dictionary that is stored. Also, it would be great if I could flatten the structure and pop up my history to be the head (root?) of the document returned.
I understand it's about projection and slicing. However, I'm not getting anywhere, despite many attempts. I get syntax errors or a full list of elements. Being rather nooby, I fear that I require a few pointers on how to diagnose my issue to begin with.
Based on the comments, I tried with aggregate and $sort. Regrettably, I only seem to be sorting the current output (that produces a single document due to the match condition). I want to access the elements inside history.
db.getCollection('things')
.aggregate([
{ $match: { _id: UUID("...") } },
{ $sort: { history: 1 } }
])
I'm sensing that I should use projection to pull out a list of elements residing under history but I'm getting no success using the below.
db.getCollection('things')
.aggregate([
{ $match: { _id: UUID("...") } },
{ $project: { history: 1, _id: 0 } }
])
It is a long process to just sort object properties by alphabetical order,
$objectToArray convert history object to array in key-value format
$unwind deconstruct above generated array
$sort by history key by ascending order (1 = ascending, -1 = descending)
$group by _id and reconstruct history key-value array
$slice to get your number of properties from dictionary from top, i have entered 1
$arrayToObject back to convert key-value array to object format
db.getCollection('things').aggregate([
{ $match: { _id: UUID("...") } },
{ $project: { history: { $objectToArray: "$history" } } },
{ $unwind: "$history" },
{ $sort: { "history.k": 1 } },
{
$group: {
_id: "$_id",
history: { $push: "$history" }
}
},
{
$project: {
history: {
$arrayToObject: { $slice: ["$history", 1] }
}
}
}
])
Playground
There is another option, but as per MongoDB, it can not guarantee this will reproduce the exact result,
$objectToArray convert history object to array in key-value format
$setUnion basically this operator will get unique elements from an array, but as per experience, it will sort elements by key ascending order, so as per MongoDB there is no guarantee.
$slice to get your number of properties from dictionary from top, i have entered 1
$arrayToObject back to convert key-value array to object format
db.getCollection('things').aggregate([
{ $match: { _id: UUID("...") } },
{
$project: {
history: {
$arrayToObject: {
$slice: [
{ $setUnion: { $objectToArray: "$history" } },
1
]
}
}
}
}
])
Playground

Update key in nested document

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

Get array of key/value pairs from document in mongodb

I'm trying to return all the key/value pairs from a document as an array.
Sample Doc :
{key:"value", key2:"value2", key3:"value3"}
Required Output :
[{key:"value"},{key2:"value2"},{ key3:"value3"}]
Ive tried using the $objectToArray operator but it doesn't work for single documents.
You can try below query :
db.collection.aggregate([
{
$project: {
_id: 0,
data: {
$map: {
input: { $objectToArray: "$$ROOT" },/** Convert object to array & iterate on each object */
in: {
/** Consider each object (k:...,v:...) as an array &
convert back to actual object (k:v) & push to 'data' array field */
$arrayToObject: [
[
"$$this"
]
]
}
}
}
}
}
])
Test : MongoDB-Playground

MongoDB query or aggregation to skip sub-documents

I'd like to create a query or aggregation where the returned documents do not include sub-documents. I do not know that a given field will be a sub-document ahead of time (or I would just use the projection to skip them). So for example, if I have a document like this:
{
_id: 1,
field1: "a",
field2: "b",
field3: {
subfield1: "c",
subfield2: "d"
}
}
When my query returns this document, it either skips field3, or replaces field3's value with something else (e.g. a string = "field_is_an_object").
As I said, I don't know ahead of time which fields will be sub-documents (or "object" types). The $redact operator was the closest I could find, but I couldn't figure out a syntax to get it to work.
There are at least two ways you can achieve what you want:
The first one is pretty concise and requires just one aggregation stage which, however, is a little bit more complex and harder to understand:
db.collection.aggregate({
$replaceRoot: { // create a new top level document
"newRoot": { // ...which shall be
$arrayToObject: { // ...created from an array
$filter: { // ...that again should contain only those elements
input: { // ...from our input array
$objectToArray: "$$ROOT" // ...which is our respective top level document transformed into an array of key-value pairs
},
cond: { // ...where
$ne: [ { $type: "$$this.v" }, "object" ] // ...the "v" (as in "value" field is not an object)
}
}
}
}
}
})
The second one I can think of is way more verbose but pretty easy to understand by adding the stages step-by-step (as always with the aggregation framework).
db.collection.aggregate({
$project: {
"tmp": { // we create a temporary field
$objectToArray: "$$ROOT" // that contains our respective root document represented as an array of key-value pairs
}
}
}, {
$unwind: "$tmp" // flatten the temporary array into multiple documents
}, {
$match: {
"tmp.v": { $not: { $type: "object" } } // filter all documents out that we do not want in our result
}
}, {
$group: { // group all documents together again
"_id": "$_id", // into one bucket per original document ("_id")
"tmp": {
$push: "$tmp" // and create an array with all the key-value pairs that have survived our $match stage
}
}
}, {
$replaceRoot: { // create a new top level document...
"newRoot": {
$arrayToObject: "$tmp" // ...out of the data we have left in our array
}
}
})