Suppose we have an array in the aggregation pipeline:
{
dates: [
"2019-01-29",
"2019-01-29",
"2019-01-29",
"2019-01-29",
"2019-02-06",
"2019-02-06",
"2019-02-06",
"2019-02-08",
"2019-06-04",
"2019-06-25",
"2019-07-26",
"2019-08-15",
"2019-08-15",
]
}
How to find an average count of the days in such an array?
The next stage of the pipeline is supposed to look like this:
dates : {
"2019-01-29": 4,
"2019-02-06": 3,
"2019-02-08": 1,
"2019-06-04": 1,
"2019-06-25": 1,
"2019-07-26": 1,
"2019-08-15": 2
}
But the final result is supposed to look like this:
avg_day_count: 1.85714285714
I.e. the average count of the days.
The sum of all days divided by the count of unique days.
You can achieve this without any $group logic with a single $project;
db.collection.aggregate([
{
"$project": {
"result": {
$divide: [
{ $size: "$dates" },
{ $size: { $setUnion: [ "$dates" ] } }
]
}
}
}
])
will give out;
[
{
"_id": ...,
"result": 1.8571428571428572
}
]
check the code interactively on MongoPlayground
You need to run $group twice using $avg in the second one:
db.collection.aggregate([
{
$uwnind: "$dates"
},
{
$group: {
_id: "$dates",
count: { $sum: 1 }
}
},
{
$group: {
_id: null,
avg_day_count: { $avg: "$count" }
}
}
])
Mongo Playground
Related
I have a "shipment" document in MongoDB that has the following basic structure:
shipment {
"id": "asdfasdfasdf",
"shipDate": "2021-04-02",
"packages": [
{
"id": "adfasdfasdfasdf",
"contents": [
{
"product": {
"id": "asdfasdfasdfasd"
},
"quantity": 10
}
]
}
]
}
Please note that "product" is stored as a DBRef.
I want to find the total quantity of a specific product (based on the product ID) that has been shipped since a given date. I believe this is the appropriate logic that should be followed:
Match shipments with "shipDate" greater than the given date.
Find entries where "contents" contains a product with an "id" matching the given product ID
Sum the "quantity" value for each matching entry
Return the sum
So far, this is what I've come up with for the Mongo query so far:
db.shipment.aggregate([
{$match: {"shipDate": {$gt: ISODate("2019-01-01")}}},
{$unwind: "$packages"},
{$unwind: "$packages.contents"},
{$unwind: "$packages.contents.product"},
{
$project: {
matchedProduct: {
$filter: {
input: "$packages.contents.products",
as: "products",
cond: {
"$eq": ["$products.id", ObjectId("5fb55eae3fb1bf783a4fa97f")]
}
}
}
}
}
])
The query works, but appears to just return all entries that meet the $match criteria with a "products" value of null.
I'm pretty new with Mongo queries, so it may be a simple solution. However, I've been unable to figure out just how to return the $sum of the "contents" quantity fields for a matching product ID.
Any help would be much appreciated, thank you.
Query Which Solved The Problem
db.shipment.aggregate([
{
$match: {
"shipDate": {$gte: ISODate("2019-01-01")},
"packages.contents.product.$id": ObjectId("5fb55eae3fb1bf783a4fa98e")
}
},
{ $unwind: "$packages" },
{ $unwind: "$packages.contents" },
{ $unwind: "$packages.contents.product" },
{
$match: {
"packages.contents.product.$id": ObjectId("5fb55eae3fb1bf783a4fa98e")
}
},
{
$group: {
"_id": null,
"total": {
"$sum": "$packages.contents.quantity"
}
}
}
])
Demo - https://mongoplayground.net/p/c3Ia9L47cJS
Use { $match: {"packages.contents.product.id": 1 } }, to filter records by product id.
After that group them back and find the total { $group: {"_id": null,"total": { "$sum": "$packages.contents.quantity" } } }
db.collection.aggregate([
{ $match: {"shipDate": "2021-04-02","packages.contents.product.id": 1 } },
{ $unwind: "$packages" },
{ $unwind: "$packages.contents" },
{ $match: { "packages.contents.product.id": 1 } },
{ $group: { "_id": null,"total": { "$sum": "$packages.contents.quantity" } } }
])
Adding extra check at top { $match: {"shipDate": "2021-04-02","packages.contents.product.id": 1 } } for product id will filter only documents with produce id we need so query will be faster.
Option-2
Demo - https://mongoplayground.net/p/eo521luylsG
db.collection.aggregate([
{ $match: { "shipDate": "2021-04-02", "packages.contents.product.id": 1 }},
{ $unwind: "$packages" },
{ $project: { contents: { $filter: { input: "$packages.contents", as: "contents", cond: {"$eq": [ "$$contents.product.id", 1] }}}}},
{ $unwind: "$contents" },
{ $group: { "_id": null, "total": { "$sum": "$contents.quantity" }}}
])
I've been breaking my head on this for a while now:
[
{
"_id": "12sdsd",
"TotalStudent": [
"10"
]
},
{
"_id": "22fdf",
"TotalStudent": [
"20"
]
}
]
I need to sum the two values after conversion from String to Integer.
This doesn't work:
db.collection.aggregate([
{
$group: {
_id: "",
TotalStudents: {
$sum: {
$toInt: "$TotalStudent.0"
}
}
}
}
])
See the playground, what am I doing wrong?
https://mongoplayground.net/p/Evm3tJUGmKa
"$TotalStudent.0" - This is not a valid syntax with aggregation queries. When working with array fields in aggregations, use Aggregation Array Operators. The operator to get an element of an array by its index is $arrayElemAt. So, the following $group stage will work fine:
{ $group: {
_id: "",
TotalStudents: { $sum: { $toInt: { $arrayElemAt: [ "$TotalStudent", 0 ] } } }
} }
I have the following documents in collection of mongodb:
banks:[{name:"ABC", amt:0},{name:"PQR", amt:-1},{name"XYZ", amt:3400}]
banks:[{name:"ABC", amt:-2},{name:"PQR", amt:2344},{name"XYZ", amt:7600}]
Like this say I have 10 documents and each document contains one banks array. Each banks array has 30 objects in it as shown above.
I am trying to write aggregation query in mongodb to get the count of objects that have "amt" less than equal to zero and greater than zero but so far unable to get it. Please help. Thanks in advance!
The output for above sample documents should be
{"greaterThanZero": 1, "lessThanEqualToZero": 2 }
{"greaterThanZero": 2, "lessThanEqualToZero": 1 }
First you have to separate yours documents with $unwind
Then with a $project and a $cond you tell for each document if it's greaterThanZero or lessThanEqualToZero
Finally you sum up greaterThanZero and lessThanEqualToZero with a $group
You can test it here : Mongo Playground
[
{
"$unwind": "$banks"
},
{
"$project": {
"greaterThanZero": {
"$cond": [
{
"$gt": [
"$banks.amt",
0
]
},
1,
0
]
},
"lessThanEqualToZero": {
"$cond": [
{
"$lte": [
"$banks.amt",
0
]
},
1,
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"greaterThanZero": {
"$sum": "$greaterThanZero"
},
"lessThanEqualToZero": {
"$sum": "$lessThanEqualToZero"
}
}
}
]
You can do it with $reduce,
it checks condition using $cond if match then add one to value,
db.collection.aggregate([
{
$project: {
lessThanEqualToZero: {
$reduce: {
input: "$banks",
initialValue: 0,
in: {
$cond: [
{ $lte: ["$$this.amt", 0] },
{ $add: ["$$value", 1] },
"$$value"
]
}
}
},
greaterThanZero: {
$reduce: {
input: "$banks",
initialValue: 0,
in: {
$cond: [
{ $gt: ["$$this.amt", 0] },
{ $add: ["$$value", 1] },
"$$value"
]
}
}
}
}
}
])
Playground
I have some MongoDb document's(representing orders) and their schema looks roughly like that:
{
id: ObjectID
exchange_order_products: Array
}
The exchange_order_products array is empty if the customer didn't exchange any items he ordered, or if they did, the array will contain an Object for each item exchanged.
I want to get the percent of orders in which the customer didn't exchange anything, e.g. exchange_order_products array is empty.
So basically the formula is the following: (Number Of Orders With At Least One Exchange * 100) / Number of Orders With No Exchanges
I know that I can count the number of orders where the exchange_order_products array is empty like that:
[{$match: {
exchange_order_products: {$exists: true, $size: 0}
}}, {$count: 'count'}]
But how do I simultaneously get the number of all the documents in my collection?
You can use $group and $sum along with $cond to count empty and non-empty ones separately. Then you need $multiply and $divide to calculate the percentage:
db.collection.aggregate([
{
$group: {
_id: null,
empty: { $sum: { $cond: [ { $eq: [ { $size: "$exchange_order_products" }, 0 ] }, 1, 0 ] } },
nonEmpty: { $sum: { $cond: [ { $eq: [ { $size: "$exchange_order_products" }, 0 ] }, 0, 1 ] } },
}
},
{
$project: {
percent: {
$multiply: [
100, { $divide: [ "$nonEmpty", "$empty" ] }
]
}
}
}
])
Mongo Playground
I am trying to get first date from inner array in mongodb object and add it to it's parent with aggregation. Example:
car: {
"model": "Astra",
"productions": [
"modelOne": {
"dateOfCreation": "2019-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
},
"modelTwo": {
"dateOfCreation": "2017-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2019-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
}
]
}
to be turned in
car: {
"model": "Astra",
"earliestDateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"earliestDateOfCreation": "2017-09-30T10:15:25.026+00:00"
}
How can I achieve that?
I'm assuming that modelOne and modelTwo are unknown when you start your aggregation. The key step is to run $map along with $objectToArray in order to get rid of those two values. Then you can just use $min to get "earliest" values:
db.collection.aggregate([
{
$addFields: {
dates: {
$map: {
input: "$car.productions",
in: {
$let: {
vars: { model: { $arrayElemAt: [ { $objectToArray: "$$this" }, 0 ] } },
in: "$$model.v"
}
}
}
}
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground
EDIT:
First step can be simplified if there's always modelOne, 'modelTwo'... (fixed number)
db.collection.aggregate([
{
$addFields: {
dates: { $concatArrays: [ "$car.productions.modelOne", "$car.productions.modelTwo" ] }
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground (2)