How to limit the result of the pipeline according to a date, week and month range? - mongodb

I have a pipeline that gives me the result according to the players belonging to a certain company in a daily, weekly, and monthly manner. I have a date filter in the dashboard which gives an iso date range. I want to limit or range my results according to the date filter? is there any way to do it in the pipeline?
if (chartType === 'Daily') {
statsPipelineCondition = {
_id: { day: { $dayOfMonth: '$createdAt' }, month: { $month: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.month': 1, '_id.day': 1 };
} else if (chartType === 'Monthly') {
statsPipelineCondition = {
_id: { month: { $month: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.month': 1 };
} else {
statsPipelineCondition = {
_id: { week: { $week: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.week': 1 };
}
const statsPipeline = [
{
$group: {
...statsPipelineCondition,
TOTAL: { $sum: 1 },
XR: { $sum: { $cond: [{ $in: ['$company', ['XR', 'CR', 'DX']] }, 1, 0] } },
CT: { $sum: { $cond: [{ $eq: ['$company', 'CT'] }, 1, 0] } },
MR: { $sum: { $cond: [{ $eq: ['$company', 'MR'] }, 1, 0] } },
MG: { $sum: { $cond: [{ $in: ['$company', ['NM', 'MM', 'MG']] }, 1, 0] } },
},
},
{
$sort: {
...sortCondition,
},
},
];
Date filter:
datefilter - { '$gte': '2020-09-01T04:49:50.899Z',
'$lte': '2020-11-03T04:49:50.899Z' }

You need to add a $match stage prior to the $group stage to filter our based on the range:
let datefilter = {
'$gte': new Date('2020-09-01T04:49:50.899Z'),
'$lte': new Date('2020-11-03T04:49:50.899Z')
};
const statsPipeline = [
{
$match: {
createdAt: datefilter
}
},
{
$group: {
...statsPipelineCondition,
TOTAL: { $sum: 1 },
XR: { $sum: { $cond: [{ $in: ['$company', ['XR', 'CR', 'DX']] }, 1, 0] } },
CT: { $sum: { $cond: [{ $eq: ['$company', 'CT'] }, 1, 0] } },
MR: { $sum: { $cond: [{ $eq: ['$company', 'MR'] }, 1, 0] } },
MG: { $sum: { $cond: [{ $in: ['$company', ['NM', 'MM', 'MG']] }, 1, 0] } },
},
},
{
$sort: {
...sortCondition,
},
},
];

Okay, So for some reason. MongoDB doesn't like dates in strings. It would be much better if we just convert the strings by an operator provided by the aggregation framework and this will make the things work.
{
$match: {
$expr: {
$and: [
{
$gte: [
'$createdAt',
{
$dateFromString: {
dateString: dateFilter.$gte,
},
},
],
},
{
$lte: [
'$createdAt',
{
$dateFromString: {
dateString: dateFilter.$lte,
},
},
],
},
],
},
},
},

Related

How to set mongodb aggregate default value?

I need to assign a default value of zero on days with zero repair, but this is the result.
[
{ day: 21, month: 10, year: 2022, count: 2 },
{ day: 28, month: 10, year: 2022, count: 1 },
{ day: 24, month: 10, year: 2022, count: 2 }
]
I just need to access the weekly repair data, 0 should be the default on non-repair days
const result = await Repair.aggregate([
{
$match: {
createdDate: {
$gte: new Date(fromDate),
$lte: new Date(toDate),
},
},
},
{
$group: {
_id: {
day: "$day",
year: "$year",
month: "$month",
},
count: {
$sum: 1,
},
},
},
{
$project: {
_id: 0,
day: "$_id.day",
month: "$_id.month",
year: "$_id.year",
count: "$count",
},
},
]);
Without valid sample input data, it is difficult to give exact solution, but would be like this one:
db.collection.aggregate([
{
$match: {
createdDate: {
$gte: new Date(fromDate),
$lte: new Date(toDate),
},
},
},
{
$group: {
_id: { $dateTrunc: { date: "$createdDate", unit: "day" } },
count: { $sum: 1 },
},
},
{ $set: { createdDate: "$_id" } },
{
$densify: {
field: "createdDate",
range: {
step: 1,
unit: "day",
bounds: "full"
}
}
},
{
$fill: {
sortBy: { createdDate: 1 },
output: { count: { value: 0 } }
}
}
]);
Mongo Playground
Update
With MongoDB version 5 the code is a bit more complex. Would be this one:
db.collection.aggregate([
{
$match: {
createdDate: {
$gt: new Date("2022-10-23T00:00:00.000Z"),
$lt: new Date("2022-10-30T00:00:00.000Z")
}
}
},
{
$facet: {
repairs: [
{
$group: {
_id: { $dateTrunc: { date: "$createdDate", unit: "day" } },
count: { $count: {} }
}
},
{
$project: {
date: "$_id",
count: "$count",
_id: 0
}
}
]
}
},
{
$set: {
allDays: {
$range: [
0,
{
$add: [
{
$dateDiff: {
startDate: { $min: "$repairs.date" },
endDate: { $max: "$repairs.date" },
/*
or
startDate: new Date("2022-10-23T00:00:00.000Z"),
endDate: new Date("2022-10-30T00:00:00.000Z"),
*/
unit: "day",
}
},
1
]
}
]
}
}
},
{
$set: {
allDays: {
$map: {
input: "$allDays",
in: {
$dateAdd: {
startDate: { $min: "$repairs.date" },
unit: "day",
amount: "$$this"
}
}
}
}
}
},
{
$project: {
repairs: {
$map: {
input: "$allDays",
as: "day",
in: {
$mergeObjects: [
{ date: "$$day", count: 0 },
{
$first: {
$filter: {
input: "$repairs",
cond: {
$eq: [
"$$day",
"$$repairs.date"
]
},
as: "repairs"
}
}
}
]
}
}
}
}
},
{
$project: {
repairs: {
$map: {
input: "$repairs",
in: "$$this.count"
}
}
}
}
])
Mongo Playground
The result cannot be simple [ 2, 0, 0, 0, 1, 2 ], the result is always a JSON document, i.e. field and values. But you can do
db.collection.aggregate([...]).toArray().shift().repairs

MongoDB - Query calculation and group multiple items

Let's say I have this data:
{"Plane":"5546","Time":"55.0", City:"LA"}
{"Plane":"5548","Time":"25.0", City:"CA"}
{"Plane":"5546","Time":"6.0", City:"LA"}
{"Plane":"5548","Time":"5.0", City:"CA"}
{"Plane":"5555","Time":"15.0", City:"XA"}
{"Plane":"5555","Time":"8.0", City:"XA"}
and more but I just visualize the data
I want to calculate and group all the time and plane, this is expected output:
{"_id:":["5546","LA"],"Sum":2,"LateRate":1,"Prob"0.5}
The sum is sum all the time, Late is sum all the time with time > "15" and Prob is Late/Sum
The code I have tried but it still is missing something:
db.Collection.aggregate([
{
$project: {
Sum: 1,
Late: {
$cond: [{ $gt: ["$Time", 15.0] }, 1, 0]
},
prob:1
}
},
{
$group:{
_id:{Plane:"$Plane", City:"$City"},
Sum: {$sum:1},
Late: {$sum: "$Late"}
}
},
{
$addFields: {
prob: {
"$divide": [
"$Late",
"$Sum"
]
}
}
},
])
db.collection.aggregate([
{
$project: {
Time: 1,
Late: {
$cond: [
{
$gt: [
{
$toDouble: "$Time"
},
15.0
]
},
"$Time",
0
]
},
prob: 1,
Plane: 1,
City: 1
}
},
{
$group: {
_id: {
Plane: "$Plane",
City: "$City"
},
Sum: {
$sum: {
"$toDouble": "$Time"
}
},
Late: {
$sum: {
$toDouble: "$Late"
}
}
}
},
{
$addFields: {
prob: {
"$divide": [
"$Late",
"$Sum"
]
}
}
}
])
Project limits the fields passed to the next stage
On string, you cannot perform all relational/arithmetic operations
Playground

How to calculate age percentage over total in MongoDB

Considering my documents are structured like the one below, I'm trying to calculate the percentage of people with a certain age over the total number of people (each document is a single person).
{
"codf": "002",
"nome": "Debora",
"cognome": "Palermo",
"datanascita": "1953-01-17"
"age": 41
}
What I tried so far is in the linked Mongo playground below: https://mongoplayground.net/p/8KF9M7f3PIT
Basically, I successfully calculated the total number of people with a certain age, but I'm failing to calculate the total amount of people in the collection to actually calculate the percentage, that because if I use the $count operator, it aggregates the documents and I don't want that.
The output should look like this:
{
"age": 41,
"percentage_with_age": <actual_percentage>
}
In order to calculate the age properly I would suggest this way:
age: {
$subtract: [
{ $subtract: [{ $year: "$$NOW" }, { $year: { $toDate: "$datanascita" } }] },
{ $cond: [{ $lt: [{ $dayOfYear: { $toDate: "$datanascita" } }, { $dayOfYear: "$$NOW" }] }, 0, 1] }
]
}
Then to get the percentage you can use this one:
db.pazienti.aggregate([
{
$project: {
datanascita: { $toDate: "$datanascita" },
age: {
$subtract: [
{ $subtract: [{ $year: "$$NOW" }, { $year: { $toDate: "$datanascita" } }] },
{ $cond: [{ $lt: [{ $dayOfYear: { $toDate: "$datanascita" } }, { $dayOfYear: "$$NOW" }] }, 0, 1] }
]
}
}
},
{ $group: { _id: "$age", count: { $sum: 1 } } },
{
$group: {
_id: null,
total: { $sum: "$count" },
age: { $push: { count: "$count", age: "$_id" } }
}
},
{
$set: {
age: {
$map: {
input: "$age",
in: {
age: "$$this.age",
count: "$$this.count",
percentage_with_this_age: { $round: [{ $multiply: [{ $divide: ["$$this.count", "$total"] }, 100] }, 2] }
}
}
}
}
},
{ $unwind: "$age" },
{ $replaceRoot: { newRoot: "$age" } },
{ $sort: { age: 1 } }
])
Or you can also first run $unwind:
db.pazienti.aggregate([
{
$project: {
datanascita: { $toDate: "$datanascita" },
age: {
$subtract: [
{ $subtract: [{ $year: "$$NOW" }, { $year: { $toDate: "$datanascita" } }] },
{ $cond: [{ $lt: [{ $dayOfYear: { $toDate: "$datanascita" } }, { $dayOfYear: "$$NOW" }] }, 0, 1] }
]
}
}
},
{ $group: { _id: "$age", count: { $sum: 1 } } },
{
$group: {
_id: null,
total: { $sum: "$count" },
age: { $push: { count: "$count", age: "$_id" } }
}
},
{ $unwind: "$age" },
{
$set: {
age: "$age.age",
count: "$age.count",
percentage_with_this_age: { $round: [{ $multiply: [{ $divide: ["$age.count", "$total"] }, 100] }, 2] },
total: "$$REMOVE"
}
},
{ $sort: { age: 1 } }
])
If you prefer $facet:
db.pazienti.aggregate([
{
$project: {
datanascita: { $toDate: "$datanascita" },
age: {
$subtract: [
{ $subtract: [{ $year: "$$NOW" }, { $year: { $toDate: "$datanascita" } }] },
{ $cond: [{ $lt: [{ $dayOfYear: { $toDate: "$datanascita" } }, { $dayOfYear: "$$NOW" }] }, 0, 1] }
]
}
}
},
{
$facet:
{
age: [{ $group: { _id: "$age", count: { $sum: 1 } } }],
total: [{ $group: { _id: null, count: { $sum: 1 } } }]
}
},
{ $unwind: "$age" },
{
$set: {
age: "$age._id",
count: "$age.count",
percentage_with_this_age: { $round: [{ $multiply: [{ $divide: ["$age.count", { $first: "$total.count" }] }, 100] }, 2] },
total: "$$REMOVE"
}
},
{ $sort: { age: 1 } }
])

How can I return a boolean field for whether the current server time is in between two date fields in a MongoDB aggregation

For each business I'm currently storing a MongoDB date for the opening and closing time on each week day. I'm storing this date as 0001-01-01T09:00:00.000Z for example because I don't want a year, month, and day associated with the time, I just want the time.
This aggregation currently correctly returns the opening and closing time for the current day as a string formatted like 09:00, but I also want to return a boolean field 'isOpen' which returns true or false for if the business is currently open based on the system time, and the opening and closing time of the current day.
I'm currently struggling to find a way to do this.
exports = async ({ limit, lat, lng, maxDistance }) => {
const METERS_PER_MILE = 1609.34
const cluster = context.services.get('mongodb-atlas');
const organizationsCollection = cluster.db('Cheers-Development').collection('organizations');
const organizations = await organizationsCollection.aggregate(
[
{
$geoNear: {
near: {
type: 'Point',
coordinates: [lat, lng]
},
maxDistance: maxDistance * METERS_PER_MILE,
spherical: true,
distanceField: 'distance',
distanceMultiplier: 0.000621371
}
},
{
$lookup: {
from: 'categories',
localField: 'categoryIds',
foreignField: '_id',
as: 'categories'
}
},
{
$addFields: {
openingTime: {
$switch: {
branches: [
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 1] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.sunday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 2] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.monday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 3] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.tuesday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 4] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.wednesday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 5] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.thursday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 6] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.friday.openingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 7] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.saturday.openingTime" } }
}
],
default: null,
}
},
closingTime: {
$switch: {
branches: [
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 1] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.sunday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 2] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.monday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 3] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.tuesday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 4] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.wednesday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 5] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.thursday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 6] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.friday.closingTime" } }
},
{
case: { $eq: [{ $dayOfWeek: "$$NOW" }, 7] },
then: { $dateToString: { format: "%H:%M", date: "$openingTimes.saturday.closingTime" } }
}
],
default: null,
}
}
}
},
{
$addFields: {
isOpen: 'Is the business currently open?'
}
}
]
).toArray();
return organizations;
};
You could use this one:
db.collection.aggregate([
{
$set: {
today: "friday",
currentTime: {
$dateFromParts: {
"year": 1, "month": 1, "day": 1,
"hour": { $hour: "$$NOW" },
"minute": { $minute: "$$NOW" },
"second": { $second: "$$NOW" },
}
}
}
},
{ $set: { openingTimes: { "$objectToArray": "$openingTimes" } } },
{
$set: {
openingTimes: {
$first: {
$filter: {
input: "$openingTimes",
cond: { $eq: [ "$$this.k", "$today" ] }
}
}
}
}
},
{
$set: {
isOpen: {
$and: [
{ $gte: [ "$openingTimes.v.openingTime", "$currentTime" ] },
{ $lt: [ "$openingTimes.v.closingTime", "$currentTime" ] }
]
}
}
}
])
See Mongo playground
Getting the weekday is not so easy (unless you do a $switch with 7 branches), I would suggest moment.js library, then it would be simply today: moment().format("ddd") or directly cond: { $eq: [ "$$this.k", moment().format("ddd") ] }

How to get sum of counted records using group by in mongodb?

I am trying to get the sum of count which I get from group, match. How can I get the same.
I have this code...
VisitorCompany.aggregate(
[
{
$match: {
$and:[
{ entry_date: { $gt: start, $lt: end } }
]
}
},
{
$group:
{
_id:
{
day: { $dayOfMonth: "$entry_date" },
month: { $month: "$entry_date" },
year: { $year: "$entry_date" }
},
count: { $sum:1 },
entry_date: { $first: "$entry_date" }
}
},
{
$project:
{
entry_date:
{
$dateToString: { format: "%Y-%m-%d", date: "$entry_date" }
},
count: 1,
_id: 0
}
},
{ $sort : { entry_date : -1 } },
])
and the output is ...
{
"count": 2,
"entry_date": "2018-12-12"
},
{
"count": 1
"entry_date": "2018-12-11"
}
Is anyone have idea that how to get sum of count i.e. 3 (2+1), means total number of records before group. thanks in advance.
Below modified query of yours will be giving you the sum of count, I have just added the
$group:{_id:"", sum:{$sum: "$count"}}}
to the existing aggregation pipeline query
Modified query
VisitorCompany.aggregate(
[
{
$match: {
$and:[
{ entry_date: { $gt: start, $lt: end } }
]
}
},
{
$group:
{
_id:
{
day: { $dayOfMonth: "$entry_date" },
month: { $month: "$entry_date" },
year: { $year: "$entry_date" }
},
count: { $sum:1 },
entry_date: { $first: "$entry_date" }
}
},
{
$project:
{
entry_date:
{
$dateToString: { format: "%Y-%m-%d", date: "$entry_date" }
},
count: 1,
_id: 0
}
},
{ $sort : { entry_date : -1 } },
{$group:{_id:"", sum:{$sum: "$count"}}}
])
The result
{ "_id" : "", "sum" : 3 }