I am trying to gather total cumulative values of each user's mission statistics, grouped by city.
This is my input:
[
{
"missions": {
"Denver": {
"savedTowers": 3,
"savedCity": 0,
"hoursFasted": 68,
"fastPointsEarned": 4080
},
"Boston": {
"savedTowers": 2,
"savedCity": 0,
"hoursFasted": 32,
"fastPointsEarned": 1920
}
}
},
{
"missions": {
"Denver": {
"savedTowers": 4,
"savedCity": 0,
"hoursFasted": 87,
"fastPointsEarned": 5220
},
"Boston": {
"savedTowers": 7,
"savedCity": 1,
"hoursFasted": 120,
"fastPointsEarned": 7200
}
}
}
]
This is the code:
db.collection("users").aggregate([
{
"$match": {
"missions": {
"$exists": true,
"$gt": {}
}
}
},
{
"$project": {
"_id": 0,
"city": {
"$objectToArray": "$missions"
}
}
},
{
"$unwind" : "$city"
},
{
"$group": {
"_id": {
"city": "$city.k"
},
"cities": {
"$addToSet": "$city.k"
},
"stats": {
"$addToSet": "$city.v"
},
"players": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0,
"city": "$_id.city",
"stats": {
"$mergeObjects": "$stats"
},
"players": "$players"
}
}
]).toArray(function(err, response) {
if (err != null) {
console.log("Error: " + err.message);
handleError(res, "Failed to fetch Mission Analytics", err.message);
} else {
res.status(200).send({ "mission_stats": response });
}
});
This is the actual output:
{
"mission_stats": [
{
"city": "Boston",
"stats": {
"savedTowers": 2,
"savedCity": 0,
"hoursFasted": 32,
"fastPointsEarned": 1920
},
"players": 2
},
{
"city": "Denver",
"stats": {
"savedTowers": 3,
"savedCity": 0,
"hoursFasted": 68,
"fastPointsEarned": 4080
},
"players": 2
}
]
}
This is the expected output:
{
"mission_stats": [
{
"city": "Boston",
"stats": {
"savedTowers": 9,
"savedCity": 0,
"hoursFasted": 152,
"fastPointsEarned": 9120
},
"players": 2
},
{
"city": "Denver",
"stats": {
"savedTowers": 7,
"savedCity": 0,
"hoursFasted": 155,
"fastPointsEarned": 9300
},
"players": 2
}
]
}
How come $mergeObjects has reduced the array of stats into just one object, but has failed to merge the values too? I'm not seeing cumulative values in the final merged object.
You are overwriting the stats with last $mergeObjects operation.
You can try below aggregation ( Not tested )
You have to convert the value object into array of key value pairs followed by $unwind+$group to group by each key and accumulate the stats. Final step to go back to named key value object.
db.colname.aggregate([
/** match stage **/
{"$project":{"city":{"$objectToArray":"$missions"}}},
{"$unwind":"$city"},
{"$addFields":{"city-v":{"$objectToArray":"$city.v"}}},
{"$unwind":"$city-v"},
{"$group":{
"_id":{"id":"$city.k","key":"$city-v.k"},
"stats":{"$sum":"$city-v.v"}
}},
{"$group":{
"_id":"$_id.id",
"players":{"$sum":1},
"stats":{"$mergeObjects":{"$arrayToObject":[[["$_id.key","$stats"]]]}}
}}
])
mergeObjects overwrites the field values as it merges the documents. If documents to merge include the same field name, the field, in the resulting document, has the value from the last document merged for the field.
I believe a better approach to take would be to sum up the various field in $city.v in the first $group operation then use a second $group operation to $push the totaled stats back together. With a final $project operation to clean up the data.
{
"$group": {
"_id": {
"city": "$city.k"
},
"savedTowersTotal": {
"$sum": "$city.v.savedTowers"
},
"savedCityTotal": {
"$sum": "$city.v.savedCity"
},
"hoursFastedTotal": {
"$sum": "$city.v.hoursFasted"
},
"fastPointsEarnedTotal": {
"$sum": "$city.v.fastPointsEarned"
},
"players": {
"$sum": 1
}
}
}, {
"$group": {
"_id": {
"city": "$_id",
"players": "$players"
},
"stats": {
"$push": {
"savedTowers": "$savedTowersTotal",
"savedCity": "$savedCityTotal",
"hoursFasted": "$hoursFastedTotal",
"fastPointsEarned": "$fastPointsEarnedTotal"
}
}
}
}, {
"$project": {
"_id": 0,
"city": "$_id.city",
"stats": 1,
"players": "$_id.players"
}
}
Related
I'm working on a project with mongoose and nodejs.
I want to get the data from one day split in every hour. And if there isn't any data I want the value to be null.
What I have so far:
const startOfDay = new Date(created_at);
startOfDay.setUTCHours(0, 0, 0, 0);
const endOfDay = new Date(created_at);
endOfDay.setUTCHours(23, 59, 59, 999);
const x = await Collection.aggregate([
{
$match: {
createdAt: { $gte: startOfDay, $lte: endOfDay },
},
},
{
$group: {
_id: { $hour: "$createdAt" },
count: { $sum: 1 },
avg: { $avg: "$some_value" },
},
},
And I get following output:
[
{
"_id": 8,
"count": 1,
"avg": 10.2
},
{
"_id": 15,
"count": 2,
"avg": 25
},
{
"_id": 12,
"count": 2,
"avg": 30
}
]
So the _id's are the hours and the other data is also correct. But what I want is:
{
"count": 5,
"avg_total": 90,
"total": 2910,
"data": [
[
{
"_id": 0,
"avg": 0,
"count": 0
},
{
"_id": 1,
"avg": 0,
"count": 0
},
...
{
"_id": 7,
"avg": 0,
"count": 0
},
{
"_id": 8,
"count": 1,
"avg": 10.2
},
...
{
"_id": 23,
"avg": 0,
"count": 0
}
]
]
}
Is there a way to achive this within the aggregation ?
my problem is how I get a simple object of ownerDetails, correct me if my code is wrong
data I got, from my code
{
"status": true,
"data": [
{
"_id": "61a2a9680b122bc154cbd6af",
"ownerName": "Ajay",
"mobile": 878787878,
"designation": "IT",
"gender": "Male",
"age": 26,
"carDetails": [
{
"carName": "BUY",
"color": "blue",
"sheets": 8,
"avgSpeed": 105,
"price": 850000,
"model": "C110"
},
{
"carName": "GTR",
"color": "blue",
"sheets": 4,
"avgSpeed": 105,
"price": 98000,
"model": "G120"
}
]
}
]
}
i want data like this,
{
"status": true,
"ownerDetails": {
"_id": "61a2a9680b122bc154cbd6af",
"ownerName": "Ajay",
"mobile": 878787878,
"designation": "IT",
"gender": "Male",
"age": 26,
"total_car": 2,
}
}
code for getting data from collections
exports.getOwnerDetails = async (req, res, next) => {
try {
Owner.aggregate([
{
$match: { ownerName: req.body.ownerName }
},
{
$lookup: {
from: "cars",
localField: "ownerName",
foreignField: "ownerName",
as: "carDetails",
},
},
{
$project: {
"carDetails._id": 0,
"carDetails.ownerName": 0,
},
},
]).then((data) => {
res.json({
status: true,
data: data,
});
});
} catch (error) {
res.status(500).json({
status: false,
msg: "Error : " + error,
});
}
};
in may returning data, I got an array of carDetails, but I only need that how many are owned by an Owner, and return a simple object of ownerDetails
$lookup return an array because more than one values can match the join. If you want to get only one value you can get the first element from the array. Then you can add these aggregation stages:
First $addFields to get the first element from the array (index 0).
Then use $project to get the desired output.
{
"$addFields": {
"data": {
"$arrayElemAt": ["$data",0]
}
}
},
{
"$project": {
"status": 1,
"ownerDetails": {
"_id": "$data._id",
"ownerName": "$data.ownerName",
"mobile": "$data.mobile",
"designation": "$data.designation",
"gender": "$data.gender",
"age": "$data.age",
"total_car": {
"$size": "$data.carDetails"
}
}
}
}
Example here
Query
i am not sure if this is what you want
unwind all owners from data
and from each owner, count its cars number
Test code here
aggregate(
[{"$unwind": {"path": "$data"}},
{"$set":
{"data.total_car": {"$size": "$data.carDetails"},
"data.carDetails": "$$REMOVE",
"_id": "$$REMOVE"}},
{"$set": {"ownerDetails": "$data", "data": "$$REMOVE"}}])
I need to add the paramater sector as an array in the group () statement.
I have the following code:
await Escaneado.aggregate([
{
$match: {
$and: [
{ "gestion": id },
{ "disponible": true }
]
},
},
{
$group: {
_id: {
code:"$codigo",
quantityTarget:"$cantidadObjetivo",
},
quantityTotalScanded : { $sum: "$cantidad" }
}
},
{
$addFields:{
difference:{ $subtract: ["$quantityTotalScanded", "$_id.quantityTarget"]}
}
},
])
output:
{
"ok": true,
"escaneadosDB": [
{
"_id": {
"code": "0V3011123A00",
"quantityTarget": 36
},
"quantityTotalScanded": 36,
"difference": 0
},
{
"_id": {
"code": "0V3011123B00",
"quantityTarget": 36
},
"quantityTotalScanded": 4,
"difference": -32
},
{
"_id": {
"code": "0V3012121D00",
"quantityTarget": 56
},
"quantityTotalScanded": 56,
"difference": 0
}
]}
output expected:
{
"ok": true,
"escaneadosDB": [
{
"_id": {
"code": "0V3011123A00",
"quantityTarget": 36,
"sector": ["A", "B", "C"]
},
"quantityTotalScanded": 36,
"difference": 0
},
{
"_id": {
"code": "0V3011123B00",
"quantityTarget": 36,
"sector": ["A"]
},
"quantityTotalScanded": 4,
"difference": -32
},
{
"_id": {
"code": "0V3012121D00",
"quantityTarget": 56,
"sector": ["A", "B"]
},
"quantityTotalScanded": 56,
"difference": 0
}
]}
I think I can add it as an array, but i do not know how implement! .
The sectors are different parameters, therefore I cannot use it as "_id". I need total quantity and the sectors in the query.
How could I do this with mongo?
this worked for me:
add this in group() sentence:
sectors: { $push: { sector: "$sector" } }
all code:
await Escaneado.aggregate([
{
$match: {
$and: [
{ "gestion": id },
{ "disponible": true }
]
},
},
{
$group: {
_id: {
code:"$codigo",
quantityTarget:"$cantidadObjetivo",
},
quantityTotalScanded : { $sum: "$cantidad" },
sectors: { $push: { sector: "$sector" } }
}
},
{
$addFields:{
difference:{ $subtract: ["$quantityTotalScanded", "$_id.quantityTarget"]}
}
},
{$sort: {"cantidadTotal": -1}},
I have been trying to come up with a query for these (simplified) documents below. My database consists of several data similar as these.
Since there is no nested querying in Mongo shell, is there another possible way to get what I want?
I am trying to get a list of Medicines that are owned by more than 30% of the pharmacies in my DB (regardless of quantity).
[
{
"Pharmacy": "a",
"Medicine": [
{
"MedName": "MedA",
"Quantity": 55
},
{
"MedName": "MedB",
"Quantity": 34
},
{
"MedName": "MedD",
"Quantity": 25
}
]
},
{
"Pharmacy": "b",
"Medicine": [
{
"MedName": "MedB",
"Quantity": 60
},
{
"MedName": "MedC",
"Quantity" : 34
}
]
}
]
How can I do this (if possible)?
Please check the answer here: https://mongoplayground.net/p/KVZ4Ee9Qhu-
var PharmaCount = db.collection.count();
db.collection.aggregate([
{
"$unwind": "$Medicine"
},
{
"$project": {
"medName": "$Medicine.MedName",
"Pharmacy": "$Pharmacy"
}
},
{
"$group": {
"_id": {
"medName": "$medName"
},
"count": {
"$sum": 1
}
}
},
{
"$project": {
"count": 1,
"percentage": {
"$concat": [
{
"$substr": [
{
"$multiply": [
{
"$divide": [
"$count",
{
"$literal": 2 // Your total number of pharmacies i.e PharmaCount
}
]
},
100
]
},
0,
3
]
},
"",
"%"
]
}
}
}
])
You should get results like:
[
{
"_id": {
"medName": "MedC"
},
"count": 1,
"percentage": "50%"
},
{
"_id": {
"medName": "MedD"
},
"count": 1,
"percentage": "50%"
},
{
"_id": {
"medName": "MedB"
},
"count": 2,
"percentage": "100%"
},
{
"_id": {
"medName": "MedA"
},
"count": 1,
"percentage": "50%"
}
]
Hope this helps.
You can not do this in a single query, but here is a way :
size = (db['01'].distinct("Pharmacy")).length;
minPN = Math.ceil(size*0.3);
db['01'].aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path : "$Medicine",
}
},
// Stage 2
{
$group: {
_id:"$Medicine.MedName",
pharmacies:{$addToSet:"$Pharmacy"}
}
},
// Stage 3
{
$project: {
pharmacies:1,
pharmacies_count:{$size:"$pharmacies"}
}
},
{
$match:{pharmacies_count:{$gte:minPN}}
}
]
);
I have the collection with following data
{
"_id": "SG01",
"name": "Pawan",
"marks": [
{
"English": 93,
"Maths": 90,
"Hindi": 89,
"Sci": 98
}
],
"__v": 0
}
{
"_id": "SG02",
"name": "Dravid",
"marks": [
{
"English": 40,
"Maths": 67,
"Hindi": 56,
"Sci": 45
}
],
"__v": 0
}
{
"_id": "SG03",
"name": "Kartik",
"marks": [
{
"English": 65,
"Maths": 77,
"Hindi": 80,
"Sci": 79
}
],
"__v": 0
}
I would like to perform the operation in which marks should be displayed as total_marks of a particular student.
As I'm newbie with mongo and know how to perform basic aggregation with sum but wasn't able to understand with arrays.. However I tried but failed to get the result.
You can use below aggregation:
db.col.aggregate([
{
$unwind: "$marks"
},
{
$project: {
_id: 1,
name: 1,
marks: {
$objectToArray: "$marks"
}
}
},
{
$project: {
_id :1,
name: 1,
total_marks: {
$reduce: {
input: "$marks",
initialValue: 0,
in: { $add : ["$$value", "$$this.v"] }
}
}
}
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
total_marks: { $sum: "$total_marks" }
}
}
])
Since your marks are stored as an object you should use $objectToArray to get an array of subjects. Then you can use $reduce to sum all subjects for one student.