Forming an array with aggregation in MongoDB - mongodb

I have a document in MongoDB 3.4 with the following structure:
{
"_id" : ObjectId("5e3419e468d01013eadb83dc"),
"id_station" : "62",
"fiware_service" : null,
"fiware_servicepath" : null,
"id_fiware_name" : "CE_del_medio",
"attrName" : "15",
"attrType" : "float",
"attrValue" : 0.33,
"id_sensor_station_absolute" : "15_62",
"recvTimeTs" : 1580387045,
"recvTime" : "2020-01-30T12:24:05.00Z",
"id_fiware" : "15",
"sensor_type" : [
{
"name" : "id",
"type" : "String",
"value" : "15"
},
{
"name" : "img",
"type" : "String",
"value" : "assets/img/contrast.png"
},
{
"name" : "manufacturer",
"type" : "String",
"value" : "Hortisis"
},
{
"name" : "medida",
"type" : "String",
"value" : "mS/cm"
},
{
"name" : "name_comun",
"type" : "String",
"value" : "CE del medio"
},
{
"name" : "place",
"type" : "String",
"value" : "interior"
},
{
"name" : "timestamp",
"type" : "DateTime",
"value" : "2020-01-30T12:24:05.00Z"
},
{
"name" : "type",
"type" : "String",
"value" : "fertigation"
}
]
}
I need to convert the sensor_type field to an array with only one object, as follows:
{
"_id":"15_62",
"medidas":[
{
"_id":"5e3419e468d01013eadb83dc",
"marca":"Hortisis",
"modelo":"Estacion",
"fabricante":"Hortisis",
"id_station":"15",
"sensor_type":[
{
"name":"15",
"type":"fertigation",
"place":"interior",
"img":"assets/img/contrast.png",
"name_comun":"Temp. Suelo",
"medida":"mS/cm"
}
],
"attrName":"15",
"attrValue":0.33,
"recvTimeTs":1580387045,
"recvTime":"2020-01-30T12:24:05.00Z",
"id_sensor_station_absolute":"15_62"
}
]
}
As you can really see it is formatting the sensor_type field = name : value.
I'm working with NODEJS and mongoose.
This is my query: (first I search, sort, only show the first value and then with the project I give format, the problem is that I don't know how to tell the project to put that format if I put "sensor_type": "$latest.attributes.name") it only shows the names and I don't know how to put it in the mentioned format.
Datagreenhouse.aggregate([
{ "$match": { "id_sensor_station_absolute": { "$in": array3 } } }, // "id_station": { "$in": id_station },
{ "$sort": { "recvTime": -1 } },
{
"$group": {
"_id": "$id_sensor_station_absolute",
"latest": { "$first": "$$ROOT" },
}
},
{
"$project": {
"_id": 1,
"id_station": "$latest.id_station",
//"id_sensor_station_absolute": "$id_sensor_station_absolute",
"attrName": "$latest.attrName",
"attrValue": "$latest.attrValue",
"recvTimeTs": "$latest.recvTimeTs",
"recvTime": "$latest.recvTime",
"id_sensor_station_absolute": "$latest.id_sensor_station_absolute",
"sensor_type": "$latest.attributes",
"name": { $arrayElemAt: ["$latest.attributes", 0] },
"type": { $arrayElemAt: ["$latest.attributes", 1] },
"place": { $arrayElemAt: ["$latest.attributes", 2] },
"img": { $arrayElemAt: ["$latest.attributes", 1] },
"name_comun": { $arrayElemAt: ["$latest.attributes", 4] },
"medida": { $arrayElemAt: ["$latest.attributes", 3] },
"interfaz": { $arrayElemAt: ["$latest.attributes", 6] },
}
}
], (err, DatagreenhouseRecuperado) => {
if (err) return res.status(500).send({ message: 'Error al realizar la peticion' + err })
if (!DatagreenhouseRecuperado) return res.status(404).send({ message: 'Error el usuario no existe' })
res.status(200).send({ DatagreenhouseRecuperado })
})
Thank you for your help. Best regards.

Since version 3.4.4, MongoDB introduced a magnific operator: $arrayToObject
This operator allows us transmute array key:value pair into object.
Syntax
RAW DATA $map $arrayToObject
sensor_type : [ sensor_type : [ sensor_type : {
{ \ { \
"name" : "manufacturer", ----> k: "manufacturer", --->
"type" : "String", / v: "Hortisis" / "manufacturer" : "Hortisis"
"value" : "Hortisis"
} }
] ] }
db.datagreenhouses.aggregate([
{
"$match": {} // setup your match criteria
},
{
"$sort": {
"recvTime": -1
}
},
{
$group: {
_id: "$id_sensor_station_absolute",
medidas: {
$push: {
_id: "$_id",
"marca": "Hortisis", // don't know where you get this value
"modelo": "Estacion", // don't know where you get this value
"id_station": "$id_station",
"attrName": "$attrName",
"attrValue": "$attrValue",
"recvTimeTs": "$recvTimeTs",
"recvTime": "$recvTime",
"id_sensor_station_absolute": "$id_sensor_station_absolute",
"sensor_type": {
$arrayToObject: {
$map: {
input: "$sensor_type",
in: {
k: "$$this.name",
v: "$$this.value"
}
}
}
}
}
}
}
}
])
MongoPlayground
[
{
"_id": "15_62",
"medidas": [
{
"_id": ObjectId("5e3419e468d01013eadb83dc"),
"attrName": "15",
"attrValue": 0.33,
"id_sensor_station_absolute": "15_62",
"id_station": "62",
"marca": "Hortisis",
"modelo": "Estacion",
"recvTime": "2020-01-30T12:24:05.00Z",
"recvTimeTs": 1.580387045e+09,
"sensor_type": {
"id": "15",
"img": "assets/img/contrast.png",
"manufacturer": "Hortisis",
"medida": "mS/cm",
"name_comun": "CE del medio",
"place": "interior",
"timestamp": "2020-01-30T12:24:05.00Z",
"type": "fertigation"
}
}
]
}
]
All you need to do is transform data to the desired result with an easy to handle object ($unwind medidas field, transform and then $group again)
Note: If your MongoDB is earlier 3.4.4 version, follow update procedure:
Install MongoDB 3.4.4 or newer
Make mongodump with new version MongoBD
Stop old MongoBD
Remove /data directory (make backup)
Start new MongoDB and run mongorestore

Related

How to update array of objects to LowerCase in mongodb?

I need to update the role in team array to lowercase.
db.users.find().pretty().limit(1)
{
"_id" : ObjectId("5d9fd81d3d598088d2ea5dc9"),
"employed" : "USA-Atlanta",
"firstName" : "Rory",
"siteRole" : "super admin",
"status" : "active",
"team" : [
{
"name" : "SALES AND MARKETING",
"displayName" : "S&M",
"role" : "Manager"
}
]
}
Tried this code.I m getting it with normal fields.
db.users.find( {}, { 'role': 1 } ).forEach(function(doc) {
db.users.update(
{ _id: doc._id},
{ $set : { 'role' : doc.role.toLowerCase() } },
{ multi: true }
)
});
sample output
"team" : [
{
"name" : "SALES AND MARKETING",
"displayName" : "S&M",
"role" : "manager"
}
]
I think the below Aggregation query is what you are looking for
var count = 0;
db.users.aggregate([
{
"$match": {
"team.role": {$exists: true}
}
},
{
"$project": {
"_id": 1,
// "team": 1,
"teamModified": {
"$map": {
"input": "$team",
"as": "arrayElems",
"in": {
"$mergeObjects": [
"$$arrayElems",
{"role": {"$toLower": "$$arrayElems.role"}}
]
}
}
}
}
},
]).forEach(function(it) {
db.users.updateOne({
"_id": it["_id"]
}, {
"$set": {
"team": it["teamModified"]
}
})
printjson(++count);
})
printjson("DONE!!!")
Note: I haven't tested the script properly in my local, so do let me know if it didn't help you out

RESTHeart filtering and sorting by sub documents property

I m working with mongodb and restheart.
In my nosql db i have a unique document with this structure:
{
"_id": "docID",
"users": [
{
"userID": "12",
"elements": [
{
"elementID": "1492446877599",
"events": [
{
"id": 1,
"date": 356
},
{
"id": 2,
"date": 123
}
]
}
]
},
{
"userID": "11",
"elements": [
{
"elementID": "14924",
"events": [
{
"id": 1,
"date": 123
},
{
"id": 2,
"date": 356
}
]
},
{
"elementID": "14925",
"events": [
{
"id": 1,
"date": 12
},
{
"id": 2,
"date": 36
}
]
}
]
}
i need to filter the user with userID = 11 and i need to order his events by ascending date.
i was trying with:
http://myhost:port/myCollection?keys={"users":{"$elemMatch":{"userID":"11"}}}&sort_by={"users.elements.events.date":-1}
but it doesn t work.
db.v.aggregate([
{ $unwind : '$users'},
{ $match : { 'users.userID' : '11' }} ,
{ $unwind : '$users.elements'},
{ $unwind : '$users.elements.events'},
{ $sort : {'users.elements.events.date': 1}},
{ $group : {
_id : '$_id',
elementID : { $first : '$users.elements.elementID' },
userID : { $first : '$users.userID' },
events : { $push : '$users.elements.events'}
}
},
{ $project : {
_id : 1,
userID : 1,
'elements.elementID' : '$elementID',
'elements.events' : '$events'
}
}
]);
This will give you following :
{
"_id" : ObjectId("5911ba55f0d9c285c561ea33"),
"userID" : "11",
"elements" : {
"elementID" : "14924",
"events" : [
{
"id" : 1,
"date" : 123
},
{
"id" : 2,
"date" : 356
}
]
}
}

Match key name and show document in Mongodb?

Json Structure:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"Number" : {
"value" : "1234567",
},
"DeviceID" : {
"value" : "01",
}
"type" : {
"value" : "ce06"}
Now i want to find only those keys document which start from /dev/.
i tried this script:
var cur = db.LIVEDATA.find({"ProductIMEIno.value":"359983007488004"});
cur.forEach(function(doc){
var keynames = Object.keys(doc);
print('the length is '+keynames.length);
for(var i=0;i<keynames.length;i++){
if(keynames[i].match(/Dev/)){
print("the name is "+keynames); }}} )
but this is not working properly.
Desired Output;
Only this document should show on the basis of key name search.
"DeviceID" : {
"value" : "01",
MongoDB isn't designed to find keys dynamically like this; it's much easier to use it to find values dynamically, so you could restructure your data structure to allow this:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : [
{
"key" : "Number",
"value" : "1234567",
},
{
"key": "DeviceID",
"value" : "01",
},
{
"key" : "type",
"value" : "ce06"
}
]
Then you will be able to query it like this:
db.LIVEDATA.aggregate([
{$match: {"ProductIMEIno.value":"359983007488004"}},
{$unwind: "$data"},
{$match: {"data.key" : /^dev/i }}
]);
That will return data structured like this:
{
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : {
"key" : "DeviceID",
"value" : "01"
}
}
Suppose you have a data collection like this:
[
{
"Number": {
"value": "1234567"
},
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"value": "01",
"name": "abc123"
},
"type": {
"value": "ce06"
}
},
{
"Number": {
"value": "1234568"
},
"DeviceID": {
"value": "02"
},
"type": {
"value": "ce07"
}
}
]
You can use following aggregation:
db.collection.aggregate([
{
"$match": {}
},
{
"$addFields": {
"root_key_value_list": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$root_key_value_list"
},
{
"$match": {
"root_key_value_list.k": {
"$regex": "^Dev"
}
}
},
{
"$group": {
"_id": "$_id",
"root_key_value_list": {
"$push": "$root_key_value_list"
}
}
},
{
"$project": {
"root": {
"$arrayToObject": "$root_key_value_list"
}
}
},
{
"$replaceRoot": {
"newRoot": "$root"
}
}
])
the result will be:
[
{
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"name": "abc123",
"value": "01"
}
},
{
"DeviceID": {
"value": "02"
}
}
]
playground:
https://mongoplayground.net/p/z5EeHALCqzy

MongoDB aggregate $group and $match with group result

I have a collection as follows
{
"_id" : ObjectId("553b2c740f12bb30f85bd41c"),
"symbol" : "EUR/GBP",
"order_id" : "PW_BarclaysTrades60530",
"ticket_id" : "PW_BarclaysTrades.60530",
"basketid" : "TESTBASKET-1428483828043",
"date_sent" : ISODate("2015-04-07T18:30:00.000Z"),
"destination" : "BarclaysTrades",
"order_price" : 0.0000000000000000,
"order_quantity" : 4000000.0000000000000000,
"order_type" : 1.0000000000000000,
"parent_quantity" : 250000000.0000000000000000,
"time_sent" : "09:03:48",
"side" : 1,
"tif" : "0",
"execution_id" : 88939,
"date_recvd" : ISODate("2015-04-07T18:30:00.000Z"),
"exe_quantity" : 50000.0000000000000000,
"time_recvd" : "09:03:48",
"execution_price" : 2.5000000000000000,
"execution_type" : 1
}
I would like to get the documents whose execution_price greater than average(execution_price) for each destination in the collection
Trying to aggregate as follows:
db.orders_by_symbol.aggregate( [
{ $limit:300000 },
{ $match:{ destination: "PAPER" } },
{ $group:{_id:{Destination:"$destination"},avg_exec_price:
{$avg:"$execution_price"} ,"data":{"$push": "$$ROOT"}}},
{$unwind:"$data"},
{$match:{execution_price:{$ne: "$avg_exec_price"}}},
{$project:{_id:0,symbol:"$data.symbol",destination:"$data.destination",
execution_id:"$data.execution_id",
exec_price:"$data.execution_price",
avg_ex_price:"$avg_exec_price"}}],
{allowDiskUse:true})
Getting the following Result
{
"result" : [
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 89109,
"exec_price" : 6.5000000000000000,
"avg_ex_price" : 95.0747920857049140
},
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 89110,
"exec_price" : 6.0000000000000000,
"avg_ex_price" : 95.0747920857049140
},
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 89111,
"exec_price" : 6.5000000000000000,
"avg_ex_price" : 95.0747920857049140
}
But when I change the '$ne' operator with '$gt' no result is being produced. Both exec_price and avg_ex_price are double datatype.Not sure why it is not working as expected.
Using MongoDB Server 3.6 and newer:
var pipeline = [
{ "$match": { "destination": "PAPER" } },
{ "$facet": {
"average": [
{ "$group": {
"_id": null,
"avg_exec_price": { "$avg": "$execution_price" }
} }
],
"data": [
{ "$project": {
"_id": 0,
"symbol": 1,
"destination": 1,
"execution_id": 1,
"execution_price": 1
} }
]
} },
{ "$addFields": {
"average": { "$arrayElemAt": ["$average", 0] }
} },
{ "$addFields": {
"data": {
"$filter" : {
"input": {
"$map": {
"input": "$data",
"as": "el",
"in": {
"symbol": "$$el.symbol",
"destination": "$$el.symbol",
"execution_id": "$$el.symbol",
"exec_price": "$$el.execution_price",
"avg_exec_price": "$average.avg_exec_price"
}
}
},
"as": "doc",
"cond": {
"$gt" : [
"$$doc.exec_price",
"$$doc.avg_exec_price"
]
}
}
}
} },
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
];
For MongoDB versions which do not support the above operators and pipelines, use the $project operator to create an additional field that stores the comparison of the two fields via the $gt aggregation operator:
var pipeline = [
{ "$match": {
"destination": "PAPER"
} },
{ "$group": {
"_id": null,
"avg_exec_price": { "$avg": "$execution_price" },
"data": { "$addToSet": "$$ROOT" }
} },
{ "$unwind": "$data" },
{ "$project": {
"_id": 0,
"data": 1,
"avg_exec_price": 1,
"isGreaterThanAverage": {
"$gt" : [ "$data.execution_price", "$avg_exec_price" ]
}
} },
{ "$match": {
"isGreaterThanAverage": true
} },
{ "$project": {
"_id": 0,
"symbol": "$data.symbol",
"destination": "$data.destination",
"execution_id": "$data.execution_id",
"exec_price": "$data.execution_price",
"avg_ex_price": "$avg_exec_price"
} }
];
Now to test the above aggregation, suppose you have the following minimum test case collection:
db.test.insert([{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 88939,
"execution_price" : 1.8
},
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 88921,
"execution_price" : 6.8
},
{
"symbol" : "USD/GBP",
"destination" : "foo",
"execution_id" : 88955,
"execution_price" : 3.1
},
{
"symbol" : "AUD/GBP",
"destination" : "PAPER",
"execution_id" : 88941,
"execution_price" : 1.1
},
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 88907,
"execution_price" : 9.4
}]);
Running the above aggregation
db.test.aggregate(pipeline);
will produce the result:
/* 0 */
{
"result" : [
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 88907,
"exec_price" : 9.4,
"avg_ex_price" : 4.775
},
{
"symbol" : "EUR/GBP",
"destination" : "PAPER",
"execution_id" : 88921,
"exec_price" : 6.8,
"avg_ex_price" : 4.775
}
],
"ok" : 1
}
After reading your questions you should use $cond in your aggregation as below :
db.collectionName.aggregate({
"$match": {
"destination": "PAPER"
}
}, {
"$group": {
"_id": "$destination",
"avg_exec_price": {
"$avg": "$execution_price"
},
"data": {
"$push": "$$ROOT"
}
}
}, {
"$unwind": "$data"
}, {
"$group": {
"_id": "$_id",
"data": {
"$push": {
"check": {
"$cond": [{
"$gt": ["$data.execution_price", "$avg_exec_price"] // check in $cond if execution_price gt avg_exec_price
}, "$data", ""] //push data if true else blank
}
}
}
}
}, {
"$unwind": "$data"
}, {
"$match": {
"data.check": {
"$exists": true, // check data.check not empty or blank
"$ne": ""
}
}
}, {
"$project": {
"_id": "$_id",
"data": "$data.check"
}
}).pretty()

Query an ArrayPosition in MongoDB

I have a collection like this:
> db.nodes.find()
{ "_id" : ObjectId("534d44e182bee8420ace927f"), "id" : "59598841", "created_by" : "JOSM", "geo" : { "type" : "Point", "coordinates" : [ 9.7346094, 52.371738 ] } }
{ "_id" : ObjectId("534d44e182bee8420ace9280"), "id" : "59598842", "created_by" : "JOSM", "geo" : { "type" : "Point", "coordinates" : [ 9.7343616, 52.3718121 ] } }
{ "_id" : ObjectId("534d44e182bee8420ace9281"), "id" : "59598845", "created_by" : "JOSM", "geo" : { "type" : "Point", "coordinates" : [ 9.7331504, 52.372057 ] } }
{ "_id" : ObjectId("534d44e182bee8420ace9282"), "id" : "59835778", "created_by" : "JOSM", "geo" : { "type" : "Point", "coordinates" : [ 9.7354137, 52.3711697 ] } }
{ "_id" : ObjectId("534d44e182bee8420ace9283"), "id" : "60409270", "created_by" : "JOSM", "geo" : { "type" : "Point", "coordinates" : [ 9.7354388, 52.3735999 ] } }
Now I want to query the coordinates-array to find the document with the greatest lon-value.
How can I do that, I have no idea :(
Tschüss, Andre
So actually getting the "lon" which is the first value, of the array may not seem immediately apparent, but is quite simple with aggregate:
db.nodes.aggregate([
{ "$project": {
"_id": {
"_id": "$_id",
"id": "$id",
"created_by": "$created_by",
"geo": "$geo",
},
"coordinates": "$geo.coordinates"
}},
{ "$unwind": "$coordinates" },
{ "$group": {
"_id": "$_id",
"lon": { "$first": "$coordinates" }
}},
{ "$sort": { "lon": 1 } },
{ "$limit": 1 },
{ "$project": {
"_id": "$_id._id",
"id": "$_id.id",
"created_by": "$_id.created_by",
"geo": "$_id.geo",
}}
])
Which gives the whole document with the higest value. Or if you just want the value:
db.nodes.aggregate([
{ "$unwind": "$geo.coordinates" },
{ "$group": {
"_id": "$_id",
"lon": { "$first": "$geo.coordinates" }
}},
{ "$group": {
"_id": null,
"lon": { "$max": "$lon" }
}}
])
Try using the aggregation framework
db.nodes.aggregate(
{ $unwind: "geo.coordinate" },
{ $group: { _id: { id: "$id"}, lon: { $first: "geo.coordinate" } } },
{ $group: { _id: null, maxval: { $max: "$lon" } } }
)
For more info on aggregation look here: http://docs.mongodb.org/manual/reference/operator/aggregation/