Building a pipeline and aggregate in Mongo - mongodb

How do I aggregate the below collection of document type to sum the quantity of all product_id sold based on each district_id and city_id within a period of time
I tried using the aggregate functions of $match, $group but haven't been successful.
{
"_id" : ObjectId("5b115e00a186ae19062b0714"),
"id" : 86164014,
"cost" : 3,
"created_date" : "2017-04-04 21:44:14",
"quantity" : 12,
"bill_id" : 46736603,
"product_id" : 24,
"bill_date" : "2017-04-04",
"district_id" : 75
"city_id": 21
}

You should be more specific about the "within a period of time" and which field we should consider, but the query for the first part could be this one:
db.getCollection("your collection").aggregate([
{
$group: {
_id: {
city_id: "$city_id",
district_id: "$district_id"
},
quantities: { $sum: "$quantity" }
}
}
])

Related

Querying aggregates on subdocuments then grouping by field in parent document

I'm a noob when it comes to Mongo and I've been struggling to wrap my head around how to fetch data in the following fashion. I have a collection of order documents that contain some data such as an event_id and a subcollection (if that's the term) of issued_tickets. issued_tickets contains one to many subdocuments that contain fields such as name, date, etc. What I am trying to do is fetch the number of each type of issued tickets for each event_id in the parent document. So I would be wanting to do a count on each issued_tickets grouped by issued_tickets.name and then that goes up to the parent which is then summed and grouped on the parent's event_id.
Can anyone help me accomplish this? I keep spinning myself out on trying groupings and projections still.
Here is a sample document:
{
"_id" : ObjectId("5ce7335c1c666f000414f74a"),
"event_id" : ObjectId("5cb54f966668a9719ef6a103"),
"subtotal" : 3000,
"service_fee" : 760,
"processing_fee" : 143,
"total" : 3903,
"customer_id" : ObjectId("5ce7666c1c335f000414f747"),
"updated_at" : ISODate("2019-05-23T23:57:17.524Z"),
"created_at" : ISODate("2019-05-23T23:57:17.524Z"),
"ref" : "60d5fcf9-86c6-469b-b86b-315a9b55caca",
"issued_tickets" : [
{
"_id" : ObjectId("5ce7335c1c335f000414f666"),
"name" : "Tier 1",
"stub_name" : "Tier 1",
"price" : 1500,
"base_fee" : 200,
"perc_fee" : "0.12",
"access_code" : "163a1b9ee98338a8a4288a1c87446665",
"redeemed" : false
},
{
"_id" : ObjectId("5ce7335c1c335f0004146669"),
"name" : "Tier 2",
"stub_name" : "Tier 2",
"price" : 1500,
"base_fee" : 200,
"perc_fee" : "0.12",
"access_code" : "f50f262cd0bf1ec4ab36667c2a762446",
"redeemed" : true
}
]
}
We can do aggregations like following
$unwind to deconstruct the array
$group to reconstruct the array. While regrouping by eventId and issued_tickets.name, we can count using $sum
Mongo script :
db.collection.aggregate([
{
$unwind: "$issued_tickets"
},
{
$group: {
_id: {
_id: "$event_id",
ticketName: "$issued_tickets.name"
},
count: {
$sum: 1
}
}
},
{
$project: {
event_id: "$_id._id",
ticketName: "$_id.ticketName",
count: 1,
_id: 0
}
}
])
Working Mongo playground

Get count of a Local field with $lookup mongodb

I want to get the count of local field specific projectId vise.For example i have below documents:
{
"_id" : ObjectId("5c0a4efa91b5021228681f7a"),
"projectId" : ObjectId("5c0a4083753a321c6c4ee024"),
"hours" : 8,
"__v" : 0
}
{
"_id" : ObjectId("5c0a4f4191b5021228681f7c"),
"projectId" : ObjectId("5c0a2a8897e71a0d28b910ac"),
"hours" : 6,
"__v" : 0
}
{
"_id" : ObjectId("5c0a4f4191b5021228681f7d"),
"projectId" : ObjectId("5c0a4083753a321c6c4ee024"),
"hours" : 2,
"__v" : 0
}
Now, I want to get the hours field count of projectId equals to 5c0a4083753a321c6c4ee024, which is 10.
Is it possible with $lookup?
$lookup is used to join two collections. And here you trying to get total counts of hours for the specific projectId. Therefore, you need to use $group here.
db.collection.aggregate([
{ "$match": { "projectId": mongoose.Types.ObjectId("5c0a4083753a321c6c4ee024") }}
{ "$group": {
"_id": "$projectId",
"totalHours": { "$sum": "$hours" }
}}
])

Summing a value of a key over multiple documents in MongoDB

I have a collection named users with the following structure to its documents
{
"_id" : <user_id>,
"NAME" : "ABC",
"TIME" : 53.0,
"OBJECTS" : 1
},
{
"_id" : <user_id>,
"NAME" : "ABCD",
"TIME" : 353.0,
"OBJECTS" : 70
}
Now, I want to sum the value of OBJECTS over the entire collection and return the value along with the objects.
Something like this
{
{
"_id" : <user_id>,
"NAME" : "ABC",
"TIME" : 53.0,
"OBJECTS" : 1
},
{
"_id" : <user_id>,
"NAME" : "ABCD",
"TIME" : 353.0,
"OBJECTS" : 70
},
"TOTAL_OBJECTS": 71
}
Or any way wherein I don't have to compute on the received object and can directly access from it. Now, I've tried looking this up but I found none where the hierarchy of the existing documents isn't destroyed.
You can use $group specifying null as a grouping id. You'll gather all documents into one array (using $$ROOT variable) and another field can represent a sum of OBJECT like below:
db.users.aggregate([
{
$group: {
_id: null,
documents: { $push: "$$ROOT" },
TOTAL_OBJECTS: { $sum: "$OBJECTS" }
}
}
])
db.users.aggregate(
// Pipeline
[
// Stage 1
{
$group: {
_id: null,
TOTAL_OBJECTS: {
$sum: '$OBJECTS'
},
documents: {
$addToSet: '$$CURRENT'
}
}
},
]
);
Into above aggregate query I have pushed all documents into an array using $addToSet operator as a part of $group stage of aggregate operation

mongodb - filter out some values when doing $unwind

I have the following Customer Order data in mongodb
"_id" : 7,
"customer name" : "John Smith",
"OrderItem" : [
{
"product_category" : "Mobile",
"price" : 900
},
{
"product_category" : "Computer",
"price" : 4200.48
},
{
"product_category" : "TV",
"price" : 670.20
},
{
"product_category" : "TV",
"price" : 960.52
}
]
I need to average each product category to be like this:
"_id" : 7,
"customer name" : "John Smith",
"OrderItem" : [
{
"product_category" : "Mobile",
"price" : 900
},
{
"product_category" : "Computer",
"price" : 4200.48
},
{
"product_category" : "TV",
"price" : 815.36
}
]
i tried to use $unwind but not sure how to group them . any help ?
Use aggregation framework with a pipeline which consists of the following stages: a $match operation in the first pipeline stage filters the document stream to allow only matching documents (document with _id = 7 in your case) to pass unmodified into the next pipeline stage, which is the $unwind operation. This deconstructs the desired OrderItem array field from the input documents to output a document for each element that you can then group on and do the aggregation operation of finding the average of the category prices. The next stage in the pipeline is the $group operation which then groups input documents by product_category and applies the $avg expression to each group on the price. The last stage $project then reshapes each document in the stream to produce the desired outcome. Thus your aggregation would look like:
db.collection.aggregate([
{
"$match": {"_id": 7}
},
{
"$unwind": "$OrderItem"
},
{
"$group": {
"_id": "$OrderItem.product_category",
"average_price": {
"$avg": "$OrderItem.price"
}
}
},
{
"$project": {
"_id": 0,
"product_category" : "$_id",
"average_price": 1
}
}
])
Result:
{
"result" : [
{
"average_price" : 4200.48,
"product_category" : "Computer"
},
{
"average_price" : 815.36,
"product_category" : "TV"
},
{
"average_price" : 900,
"product_category" : "Mobile"
}
],
"ok" : 1
}
First you should unwind OrderItem then group them and mongo $avg to calculate avarage. Below aggregation will calculate avg
db.collectionName.aggregate(
{"$match":{"customer name":"John Smith"}}, // match specified customername
{"$unwind":"$OrderItem"}, // unwind the OrderItem
{"$group":{"_id":"$OrderItem.product_category",
"avg": {"$avg":"$OrderItem.price"} // mongo avg method used for avrage
}}
).pretty()
So above query return following results
{ "_id" : "Computer", "avg" : 4200.48 }
{ "_id" : "TV", "avg" : 815.36 }
{ "_id" : "Mobile", "avg" : 900 }
But above result not match your given expected output, so you should group twice to get exact output
db.collectionName.aggregate(
{"$match":{"customer name":"John Smith"}}, //match given criteria
{"$unwind":"$OrderItem"}, //unwind $OrderItem
{"$group":{"_id":"$OrderItem.product_category",
"customerName":{"$first":"$customer name"}, // group all data with calculating avg
"id":{"$first":"$_id"},
"avg":{"$avg":"$OrderItem.price"}}},
{"$group":{"_id":"$id",
"customer Name":{"$first":"$customerName"},
"OrderItem":{"$push": {"product_category":"$_id","price":"$avg"}}}} // group them for expected output
).pretty()
.aggregate([
{$unwind: "$OrderItem"},
{$group: {
_id: {id: "$_id", cat: "$OrderItem.product_category"},
name: {$first: "$customer name"},
price: {$avg: "$OrderItem.price"}
}},
{$group: {
_id: "$_id.id",
OrderItem: {$push: {product_category: "$_id.cat", price: "$price"}},
"customer name": {$first: "$name"}
}}
])

How to average the summed up values in mongodb?

Using MongoDB 2.4.8,
I have the following records
{
"category" : "TOYS",
"price" : 12,
"status" : "online",
"_id" : "35043"
}
{
"category" : "TOYS",
"price" : 13,
"status" : "offline",
"_id" : "35044"
}
{
"category" : "TOYS",
"price" : 22,
"status" : "online",
"_id" : "35045"
}
{
"category" : "BOOKS",
"price" : 13,
"status" : "offline",
"_id" : "35046"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "online",
"_id" : "35047"
}
I want to find the average price of each category whose status is online and total price is more than 50.
I am not sure how to construct this query.
So far, I can construct the query where I summed up and find out the total price for each category whose status is online.
db.products.aggregate([
{"$match":
{
{status:"online"}
}
},
{"$group" :
{
"_id": "$category",
"total_price": {$sum:"$price"},
}
}
])
I am not sure how to add more stages to this query to get the averages I am looking for.
You can just add more stages to your aggregation pipeline. For example:
db.items.aggregate([
{$match:
{
status:"online"
}
},
{$group :
{
_id: "$category",
total_price: {$sum:"$price"},
}
},
{$match:
{
total_price:{$gt:50}
}
},
{$group :
{
_id: "1",
avg_price: {$avg:"$total_price"},
}
},
]);
EDITTED based on clarifications
You can calculate the average product price per category in the $group step with the total, and then add an extra $match stage to limit the results to products with total of more than 50:
db.products.aggregate(
// Find matching products (can take advantage of index)
{ $match: {
status: "online"
}},
// Calculate total and average
{ $group: {
"_id": "$category",
"total_price": { $sum:"$price" },
"avg_price": { $avg:"$price"}
}},
// Limit results to price > 50
{ $match: {
"total_price" : { $gt: 50 }
}}
)
Note that with your example data, there would be no matching results for $gt:50 (you could instead try with $gt:30 to get the "TOYS" category as a match with total price of 34).
Averaging total prices for matching categories
If you want to get the average price for the total prices of the categories matching the limit, you can add an extra $group step at the end:
// Calculate the average total price
{ $group: {
"_id": null,
"total_average_price": { $avg:"$total_price"}
}}
Note that this extra grouping is going to reduce everything down to one number (the total_average_price) which may or may not be what you expect. You might want to save the intermediate results before running the aggregation with the last group, or just calculate the average in your application code if there aren't a lot of numbers to sum up.