Query to get a value by subtracting a value from current and next document - mongodb

I have a mongo db collection like below,
{
"id": ObjectId("132456"),
reading :[
{
"weight" : {
"measurement" : 82.0,
"unit" : "kg"
}
}
],
"date" : ISODate("2018-09-12T11:45:08.174Z")
},
{
"id": ObjectId("132457"),
reading :[
{
"weight" : {
"measurement" : 80.0,
"unit" : "kg"
}
}
],
"date" : ISODate("2018-09-12T10:45:08.174Z")
},
{
"id": ObjectId("132458"),
reading :[
{
"weight" : {
"measurement" : 85.0,
"unit" : "kg"
}
}
],
"date" : ISODate("2018-09-11T09:45:08.174Z")
}
I need a mongo db query that will give me the current weight and the weight difference between the current and next record.
Example output below,
{
"id": ObjectId("132456"),
"currentWeight": 75.0,
"weightDifference": 2.0,
"date" : ISODate("2018-09-12T11:45:08.174Z")
},
{
"id": ObjectId("132457"),
"currentWeight": 80.0,
"weightDifference": -5.0,
"date" : ISODate("2018-09-12T10:45:08.174Z")
}
I was not able to get the weight from next document to subtract the weight from current document.
Thanks in advance for your help
My try for the above problem,
db.measurementCollection.aggregate([
{
$match : { "date" : { $gte : new ISODate("2018-09-01T00:00:00.000Z") , $lte : new ISODate("2018-09-12T23:59:59.000Z") } }
},
{
$project : { "date" : 1 ,
"currentWeight" : {$arrayElemAt: [ "$reading.weight.measurement", 0 ]}
},
{ $sort: {"date":-1} },
{
$addFields : {
"weigtDifference" :
{
{
$limit: 2
},
{
$group: {
_id: null,
'count1': {$first: '$currentWeight'},
'count2': {$last: '$currentWeight'}
}
},
{
$subtract: ['$count1', '$count2']
}
}
}
}
])

You can try below aggregation but I will not recommend you to use this with the large data set.
db.collection.aggregate([
{ "$match": {
"date" : {
"$gte": new ISODate("2018-09-01T00:00:00.000Z"),
"$lte": new ISODate("2018-09-12T23:59:59.000Z")
}
}},
{ "$unwind": "$reading" },
{ "$sort": { "date": -1 }},
{ "$group": { "_id": null, "data": { "$push": "$$ROOT" }}},
{ "$project": {
"data": {
"$filter": {
"input": {
"$map": {
"input": { "$range": [0, { "$size": "$data" }] },
"as": "tt",
"in": {
"$let": {
"vars": {
"first": { "$arrayElemAt": ["$data", "$$tt"] },
"second": { "$arrayElemAt": ["$data", { "$add": ["$$tt", 1] }] }
},
"in": {
"currentWeight": "$$first.reading.weight.measurement",
"weightDifference": { "$subtract": ["$$second.reading.weight.measurement", "$$first.reading.weight.measurement"] },
"_id": "$$first._id",
"date": "$$first.date"
}
}
}
}
},
"cond": { "$ne": ["$$this.weightDifference", null] }
}
}
}
},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" }}
])

Related

Simple MongoDB Aggregation

I'm a bit confused on how to group using aggregation but still be able to extract specific values from arrays:
db.collection.aggregate([
{ "$unwind": f"${stat_type}" },
{
"$group": {
"_id": "$userId",
"value" : { "$max" : f"${stat_type}.stat_value" },
"character" : f"${stat_type}.character_name", <-- how do I extract this value that matches where the $max from above is grabbed.
}
},
{ "$sort": { "value": -1 }},
{ '$limit' : 30 }
])
Sample Entries:
{
'name' : "Tony",
'userId' : 12345,
'damage_dealt' : [
"character_name" : "James",
"stat_value" : 100243
]
}
{
'name' : "Jimmy",
'userId' : 12346,
'damage_dealt' : [
"character_name" : "James",
"stat_value" : 1020243
]
}
{
'name' : "Tony",
'userId' : 12345,
'damage_dealt' : [
"character_name" : "Lebron",
"stat_value" : 99900243
]
}
A sample output for what I'm looking for is below:
[
{
'_id':12345,
'user' : 'Tony'
'character_name' : 'Lebron',
'stat_value' : 99900243
},
{
'_id':12346,
'user' : 'Jimmy'
'character_name' : 'James',
'stat_value' : 1020243
}
]
You can use the $top accumulator to achieve the desired result. Like this:
db.collection.aggregate([
{
"$unwind": "$damage_dealt"
},
{
"$group": {
"_id": "$userId",
"value": {
$top: {
output: {
character_name: "$damage_dealt.character_name",
stat_value: "$damage_dealt.stat_value"
},
sortBy: {
"damage_dealt.stat_value": -1
}
}
},
}
},
{
"$project": {
character_name: "$value.character_name",
stat_value: "$value.stat_value"
}
},
{
"$sort": {
"stat_value": -1
}
},
{
"$limit": 30
}
])
Playground link.
Or collects all the group elements in an array, and the max stat_value, then pick the object from the array containing the max stat_value.
db.collection.aggregate([
{
"$unwind": "$damage_dealt"
},
{
"$group": {
"_id": "$userId",
"max_stat": {
"$max": "$damage_dealt.stat_value"
},
"damages": {
"$push": {
name: "$name",
damage_value: "$damage_dealt"
}
}
}
},
{
"$project": {
"damages": {
"$arrayElemAt": [
{
"$filter": {
"input": "$damages",
"as": "damage",
"cond": {
"$eq": [
"$$damage.damage_value.stat_value",
"$max_stat"
]
}
}
},
0
]
}
}
},
{
"$project": {
"character_name": "$damages.damage_value.character_name",
"stat_value": "$damages.damage_value.stat_value",
"name": "$damages.name"
}
},
{
"$sort": {
"stat_value": -1
}
},
{
"$limit": 30
}
])
Playground link.
Here's another way you could do it.
db.collection.aggregate([
{
"$group": {
"_id": "$userId",
"user": {"$first": "$name"},
"damage_dealts": {"$push": "$damage_dealt"},
"maxStat": {"$max": {"$first": "$damage_dealt.stat_value"}}
}
},
{
"$set": {
"outChar": {
"$first": {
"$arrayElemAt": [
"$damage_dealts",
{"$indexOfArray": ["$damage_dealts.stat_value", "$maxStat"]}
]
}
}
}
},
{
"$project": {
"user": 1,
"character_name": "$outChar.character_name",
"stat_value": "$outChar.stat_value"
}
},
{"$sort": {"stat_value": -1}},
{"$limit": 30}
])
Try it on mongoplayground.net.

Mongodb query to get count of field based on the value for a matching string

I have the following Mongodb document.
{
"_id" : ObjectId("62406bfaa1d66f8d99c6e97d"),
"skill": "Programming Language"
"supply" : [
{
"employeeName" : "A1",
"skillRating" : 3
},
{
"employeeName" : "A2",
"skillRating" : 4
},
{
"employeeName" : "A3",
"skillRating" : 4
},
{
"employeeName" : "A4",
"skillRating" : 4
},
{
"employeeName" : "A5",
"skillRating" : 3
},
{
"employeeName" : "A6",
"skillRating" : 4
},
{
"employeeName" : "A7",
"skillRating" : 2
},
{
"employeeName" : "A8",
"skillRating" : 2
},
{
"employeeName" : "A9",
"skillRating" : 4
},
{
"employeeName" : "A10",
"skillRating" : 3
},
{
"employeeName" : "A11",
"skillRating" : 3
},
{
"employeeName" : "A12",
"skillRating" : 3
},
{
"employeeName" : "A13",
"skillRating" : 2
},
{
"employeeName" : "A14",
"skillRating" : 4
},
{
"employeeName" : "A15",
"skillRating" : 4
}
]
}
How can I write a Mongodb query to produce the following output (i.e.: Get the count of occurrence of each value for a matching skill)
{
skillName : "Programming Language",
skillRating1: 0, <-- Count of skillRating with value 1
skillRating2: 3, <-- Count of skillRating with value 2
skillRating3: 5, <-- Count of skillRating with value 3
skillRating4: 7, <-- Count of skillRating with value 4
skillRating5: 0 <-- Count of skillRating with value 5
}
[Note: I am learning to write Mongodb queries]
You can go with aggregation,
$unwind to deconstruct the array
$group to get the sum of avg by _id and the avg
$arrayToObject to make the field to object with the help of $concat. Because we need the skillRating1,skillRating2...
$replaceRoot to get the object to root document
$project to decide whether to show or not
Here is the code,
db.collection.aggregate([
{ "$unwind": "$supply" },
{
"$group": {
"_id": { _id: "$_id", avg: "$supply.avgSkillRating" },
"count": { "$sum": 1 },
"skill": { "$first": "$skill" }
}
},
{
"$group": {
"_id": "$_id._id",
"skill": { "$first": "$skill" },
"data": {
$push: {
k: {
$concat: [ "avgSkillRating", { $toString: "$_id.avg" } ]
},
v: "$count"
}
}
}
},
{ "$addFields": { "data": { "$arrayToObject": "$data" } } },
{
"$replaceRoot": {
"newRoot": { "$mergeObjects": [ "$$ROOT", "$data" ] }
}
},
{ "$project": { data: 0 } }
])
Working Mongo playground
Maybe something like this:
db.collection.aggregate([
{
$unwind: "$supply"
},
{
$group: {
_id: "$supply.avgSkillRating",
cnt: {
$push: "$supply.avgSkillRating"
},
skill: {
$first: "$skill"
}
}
},
{
$project: {
z: [
{
"k": {
"$concat": [
"avgSkillRating",
{
$toString: "$_id"
}
]
},
"v": {
$size: "$cnt"
}
}
],
skill: 1
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
{
"$arrayToObject": "$z"
},
{
skillName: "$skill"
}
]
}
}
},
{
$group: {
_id: "$skillName",
x: {
$push: "$$ROOT"
}
}
},
{
"$replaceRoot": {
"newRoot": {"$mergeObjects": "$x"}
}
}
])
Explained:
Unwind the supply array
group avgSkillRating to array cnt ( to be possible to count )
form z array with k,v suitable for arrayToObject
mergeObjects to form the keys and values
group to join the objects and leave only single skillName
replace the root document with the newly formed document with the necesary details.
playground
Here's another version that also reports skillRatings with a zero count. This aggregation pipeline is essentially identical to #varman's answer and adds a complex (to me anyway) "$set"/"$map" to create the extra fields.
db.collection.aggregate([
{
"$unwind": "$supply"
},
{
"$group": {
"_id": { "_id": "$_id", "avg": "$supply.avgSkillRating" },
"count": { "$count": {} },
"skillName": { "$first": "$skill" }
}
},
{
"$group": {
"_id": "$_id._id",
"skillName": { "$first": "$skillName" },
"data": {
"$push": {
"_r": "$_id.avg",
"k": { $concat: [ "skillRating", { $toString: "$_id.avg" } ] },
v: "$count"
}
}
}
},
{
"$set": {
"data": {
"$map": {
"input": { "$range": [ 1, 6 ] },
"as": "rate",
"in": {
"$let": {
"vars": {
"idx": { "$indexOfArray": [ "$data._r", "$$rate" ] }
},
"in": {
"$cond": [
{ "$gte": [ "$$idx", 0 ] },
{
"k": {
"$getField": {
"field": "k",
"input": { "$arrayElemAt": [ "$data", "$$idx" ] }
}
},
"v": {
"$getField": {
"field": "v",
"input": { "$arrayElemAt": [ "$data", "$$idx" ] }
}
}
},
{
"k": { $concat: [ "skillRating", { $toString: "$$rate" } ] },
"v": 0
}
]
}
}
}
}
}
}
},
{ "$set": { "data": { "$arrayToObject": "$data" } } },
{ "$replaceWith": { "$mergeObjects": [ "$$ROOT", "$data" ] } },
{ "$unset": [ "data", "_id" ] }
])
Try it mongoplayground.net.

Counting the two value in a attribute using aggregate in mongodb

I have some documents in a collection which looks like this
{
"_id" : "5a2e50b32d43ba00010041e5",
account_id:"23232323"
status:"accepted",
keyname:"java"
},
{
"_id" : "5a2e54332d43ba00010041e5",
account_id:"2323233"
status:"pending",
keyname:"java"
},
{
"_id" : "5a2e54332d43ba00010041e5",
account_id:"23232sdsd3"
status:"pending",
keyname:"Nodejs"
}
I need to get the counts of the pending and accepted status for each keyname for a particular account_id
eg: should give a result like this.
{
keyname:"java",
pending:10,
accepted:10
}
This is the code that I have tried out
db.getCollection("programs").aggregate([
{ "$match": { "account_id": "1" } },
{ "$group": { "_id": "$keyname", "count": { "$sum": 1 } } },
{ "$match": { "_id": { "$ne": null } } }
])
which gives a result like this
{
"_id" : "java",
"count" : 3.0
},
{
"_id" : "nodejs",
"count" : 3.0
},
{
"_id" : "C#",
"count" : 3.0
}
You can use below aggregation
db.collection.aggregate([
{ "$match": { "account_id": "1" } },
{ "$group": {
"_id": "$keyname",
"accepted": {
"$sum": {
"$cond": [
{ "$eq": ["$status", "accepted"] },
0,
1
]
}
},
"pending": {
"$sum": {
"$cond": [
{ "$eq": ["$status", "pending"] },
0,
1
]
}
}
}}
])

Combine results based on condition during group by

Mongo query generated out of java code:
{
"pipeline": [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},
{
"$group": {
"_id": "$result",
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}]
}
Field 'result' can have values like Approved, Rejected, null and "" (empty string). What I am trying to achieve is combining the count of both null and empty together.
So that the empty string Id will have the count of both null and "", which is equal to 4
I'm sure theres a more "proper" way but this is what i could quickly come up with:
[
{
"$group" : {
"_id" : "$result",
"id" : {
"$first" : "$result"
},
"labelKey" : {
"$first" : {
"$ifNull" : [
"$result",
"$result"
]
}
},
"value" : {
"$sum" : 1.0
}
}
},
{
"$group" : {
"_id" : {
"$cond" : [{
$or: [
{"$eq": ["$_id", "Approved"]},
{"$eq": ["$_id", "Rejected"]},
]}},
"$_id",
""
]
},
"temp" : {
"$push" : {
"_id" : "$_id",
"labelKey" : "$labelKey"
}
},
"count" : {
"$sum" : "$value"
}
}
},
{
"$unwind" : "$temp"
},
{
"$project" : {
"_id" : "$temp._id",
"labelKey": "$temp.labelKey",
"count" : "$count"
}
}
],
);
Due to the fact the second group is only on 4 documents tops i don't feel too bad about doing this.
I have used $facet.
The MongoDB stage $facet lets you run several independent pipelines within the stage of a pipeline, all using the same data. This means that you can run several aggregations with the same preliminary stages, and successive stages.
var queries = [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},{
$facet: {//
"empty": [
{
$match : {
result : { $in : ['',null]}
}
},{
"$group" : {
"_id" : null,
value : { $sum : 1}
}
}
],
"non_empty": [
{
$match : {
result : { $nin : ['',null]}
}
},{
"$group" : {
"_id" : '$result',
value : { $sum : 1}
}
}
]
}
},
{
$project: {
results: {
$concatArrays: [ "$empty", "$non_empty" ]
}
}
}];
Output :
{
"results": [{
"_id": null,
"value": 52 // count of both '' and null.
}, {
"_id": "Approved",
"value": 83
}, {
"_id": "Rejected",
"value": 3661
}]
}
Changing the group by like below solved the problem
{
"$group": {
"_id": {
"$ifNull": ["$result", ""]
},
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}

Aggregation error: $arrayElemAt's first argument must be an array, but is object

I'm trying to aggregate a collection in mongo using the following pipeline:
const results = await Price.aggregate([
{ $match: { date: today } },
{ $unwind: '$points' },
{ $match: { 'points.time': { $gte: start, $lte: now } } },
{ $sort: { 'points.time': 1 } },
{ $project: {
'high': { $max: '$points.price' },
'low': { $min: '$points.price' },
'open': { $arrayElemAt: ['$points', 0] },
'close': { $arrayElemAt: ['$points', -1] }
} }
])
However the $arrayElemAt operator isn't working preseumably because one of the preceding stages ($unwind I believe) converts the array of points I have in my documents to an object. How can I fix this?
Example document:
{
"_id" : ObjectId("5c93ac3ab89045027259a23f"),
"date" : ISODate("2019-03-21T00:00:00Z"),
"symbol" : "CC6P",
"points" : [
{
"_id" : ObjectId("5c93ac3ab89045027259a244"),
"volume" : 553,
"time" : ISODate("2019-03-21T09:35:34.239Z"),
"price" : 71
},
{
"_id" : ObjectId("5c93ac3ab89045027259a243"),
"volume" : 1736,
"time" : ISODate("2019-03-21T09:57:34.239Z"),
"price" : 49
},
....
],
My expected result is an array of objects where the points that should be passed to the project stage should be points in the specified range in the second $match. I tried combining the two $match stages and removing the $unwind stage and the error is gone however the time range isn't being applied
I believe you are missing a $group stage to rollback your points array
const results = await Price.aggregate([
{ "$match": { "date": today } },
{ "$unwind": "$points" },
{ "$match": { "points.time": { "$gte": start, "$lte": now } } },
{ "$sort": { "points.time": 1 } },
{ "$group": {
"_id": "$_id",
"points": { "$push": "$points" },
"date": { "$first": "$date" },
"symbol": { "$first": "$symbol" }
}},
{ "$project": {
"high": { "$max": "$points.price" },
"low": { "$min": "$points.price" },
"open": { "$arrayElemAt": ["$points", 0] },
"close": { "$arrayElemAt": ["$points", -1] }
}}
])