display week of day for date stored in mongoDB - javascript

I want to display for each day in the last 7 days mount of purchases in the following format:
{
"sunday":30,
"monday":20,
...
}
one purchase on the database looks like this:
{
_id: 603fcbcc691d8a5ecc320059
productId: "603fc917a569f565687e2626"
clientId: "1"
totalPrice: 50
date: 2021-03-02T00:00:00.000+00:00 // a date object
}
Purchase.aggregate([
{ "$match": { "date": {$gte: new Date((new Date().getTime() - (7 * 24 * 60 * 60 * 1000))) } } },
{ "$group": {
"_id": { "day": { $substrCP: [ "$date", 0, 10 ] } },
"count": { $sum: 1 }
}},
{ "$sort" : { "_id.day": 1}},
])
i run this code and i got:
[
{
"_id": {
"day": "2021-02-28"
},
"count": 30
},
{
"_id": {
"day": "2021-03-01"
},
"count": 20
}
]

instead of using switch statement i used this:
before agregate() i initialized an array with values:
const days = ['','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
and did the agregate like that:
Purchase.aggregate([
{ "$match": { "date": { $gte: new Date((new Date().getTime() - (7 * 24 * 60 * 60 * 1000))) } } },
{
"$group": {
"_id": "$date",
"count": { $sum: 1 }
}
},
{ "$sort": { "_id": 1 } },
{
"$project": {
"_id": 0,
"count": 1,
"day": { $arrayElemAt: [ days, { $dayOfWeek: "$_id" } ] }
}
},
{
"$group": {
"_id": null,
"data": { $push: { k: "$day", v: "$count" } }
}
},
{
"$project": {
"data": { $arrayToObject: "$data" },
"_id": 0
}
}
])
i run this and i got the wanted answer:
[
{
"data": {
"Sun": 30,
"Mon": 20
}
}
]

This may help you, but I could not find any readymade method in mongodb which gives me Sunday for value 1. thats why keeping switch statement.
collection.aggregate([
{ "$match": { "date": { $gte: new Date((new Date().getTime() - (7 * 24 * 60 * 60 * 1000))) } } },
{
"$group": {
"_id": { $dayOfWeek: "$date" },
"count": { $sum: 1 }
}
},
{ "$sort": { "_id": 1 } },
{
$group: {
_id: null,
data: {
$push: {
k: {
$switch: {
branches: [
{
case: { $eq: ["$_id", 1] },
then: "monday"
},
{
case: { $eq: ["$_id", 2] },
then: "tuesday"
},
{
case: { $eq: ["$_id", 3] },
then: "wednesday"
},
{
case: { $eq: ["$_id", 4] },
then: "thursday"
},
{
case: { $eq: ["$_id", 5] },
then: "friday"
},
{
case: { $eq: ["$_id", 6] },
then: "saturday"
},
{
case: { $eq: ["$_id", 7] },
then: "sunday"
}]
}
},
v: "$count"
}
}
}
},
{
$project: {
data: { $arrayToObject: "$data" }
}
}
])

Related

MongoDB $addFields, add boolean field based on condition

I have a list of places documents which have opening times, which is an object detailed below:
"openingTimes": {
"Wednesday": [ // array of shifts
{
"startTime": { "hour": 1, "minute": 0 }, // shift starts
"endTime": { "hour": 10, "minute": 0} //shift ends
},
{
"startTime": { "hour": 15, "minute": 0 },
"endTime": { "hour": 23, "minute": 59 }
}
],
"Thursday": [
{
"startTime": { "hour": 0, "minute": 0 },
"endTime": { "hour": 23, "minute": 59 }
}
],
}
I need to add a field to indicate if the place is open now or not,
{
$addFields: {
isOpenNow: {
$and: [
{
$or: [
{
$lt: ['$openingTimes.Wednesday.startTime.hour', 14]
},
{
$and: [
{
$lte: ['$openingTimes.Wednesday.startTime.hour', 14]
},
{
$lte: ['$openingTimes.Wednesday.startTime.minute', 0]
}
]
}
]
},
{
$or: [
{
$gt: ['$openingTimes.Wednesday.endTime.hour', 14]
},
{
$and: [
{
$gte: ['$openingTimes.Wednesday.endTime.hour', 14]
},
{
$gte: ['$openingTimes.Wednesday.endTime.minute', 0]
}
]
}
]
}
]
}
but it ends up with a result of false I think $gte: ['$openingTimes.Wednesday.endTime.hour', 14] compares the array of hours to 14, is there is a way to indicate if the place is open based on it's shifts.
You can try below aggregate:
{
isPlaceOPen: {
$cond: {
if: { $gt: [{ $size: {
$filter: {
input: '$openingTimes.Wednesday',
as: 'shift',
cond: {
$and: [
{
$or: [
{
$lt: [
'$$shift.startTime.hour',
14
]
},
{
$and: [
{
$lte: [
'$$shift.startTime.hour',
14
]
},
{
$lte: [
'$$shift.startTime.minute',
0
]
}
]
}
]
},
{
$or: [
{
$gt: [
'$$shift.endTime.hour',
14
]
},
{
$and: [
{
$gte: [
'$$shift.endTime.hour',
14
]
},
{
$gte: [
'$$shift.endTime.minute',
0
]
}
]
}
]
}
]
}
}
} }, 0] },
then: true,
else: false,
},
}
}

Mongodb Aggregate - Grouping by hour for today

I was wondering if someone could help me get my aggregation function right. I'm trying to count load of visits of my Fitness. I have Visits table where every visit (from, to) dates are saved. Also sub-visits to some services saved too.
[{
"from": ISODate("2020-01-17T10:23:27.645Z"),
"to": ISODate("2020-01-17T12:23:28.760Z"),
"visits": [
{
"from": ISODate("2020-01-17T10:23:27.646Z"),
"to": ISODate("2020-01-17T10:30:28.760Z"),
"service": ObjectId("5e05f17d6b7f7920d4a62403")
},
{
"from": ISODate("2020-01-17T10:30:29.760Z"),
"to": ISODate("2020-01-17T12:23:28.760Z"),
"service": ObjectId("5d05f17dt57f7920d4a62404")
}
],
...
},
{
"from": ISODate("2020-01-17T10:40:00.000Z"),
"to": ISODate("2020-01-17T11:30:28.760Z"),
"visits": [
{
"from": ISODate("2020-01-17T10:40:00.000Z"),
"to": ISODate("2020-01-17T11:30:28.760Z"),
"service": ObjectId("h505f17s6b2f7920d4a6295y")
}
],
...
}
]
I wanted to reach the result for today from 00:00 if current time is 13:35 then get until 13:00 like :
[{
'date': '2020-01-18T00:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T00:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T00:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T01:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T02:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T03:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T04:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T05:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T06:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T07:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T08:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T09:00:0.000Z'
'visits': 0
},
{
'date': '2020-01-18T010:00:0.000Z'
'visits': 2
},
{
'date': '2020-01-18T11:00:0.000Z'
'visits': 2
},
{
'date': '2020-01-18T12:00:0.000Z'
'visits': 1
},
{
'date': '2020-01-18T13:00:0.000Z'
'visits': 0
}]
Tried too many examples, but nothing worked, Please help !
EXPLANATION
If we use $$NOW, we get current date. If you setup currDate, today, nextDay manually, MongoDB aggregation will limit desired values.
Now, we calculate how many hours from today (yyyy-MM-dd 00:00:00) - currDate (yyyy-MM-dd 13:35:00) ~ 13 hours and with $range operator create array
We flatten interval and apply filtering
You may use such query:
db.collection.aggregate([
{
$addFields: {
nextDay: {
$add: [
{
$dateFromString: {
dateString: {
$substr: [
{
$toString: "$$NOW"
},
0,
10
]
},
format: "%Y-%m-%d"
}
},
{
$multiply: [
24,
60,
60,
1000
]
}
]
},
today: {
$dateFromString: {
dateString: {
$substr: [
{
$toString: "$$NOW"
},
0,
10
]
},
format: "%Y-%m-%d"
}
},
currDate: {
$dateFromString: {
dateString: {
$substr: [
{
$toString: "$$NOW"
},
0,
13
]
},
format: "%Y-%m-%dT%H",
timezone: "-01"
}
}
}
},
{
$addFields: {
interval: {
$range: [
0,
{
$add: [
{
$divide: [
{
$subtract: [
"$currDate",
"$today"
]
},
{
$multiply: [
60,
60,
1000
]
}
]
},
1
]
},
1
]
}
}
},
{
$unwind: "$interval"
},
{
$addFields: {
date: {
$add: [
"$today",
{
$multiply: [
"$interval",
60,
60,
1000
]
}
]
},
nextHour: {
$add: [
"$today",
{
$multiply: [
{
$add: [
"$interval",
1
]
},
60,
60,
1000
]
}
]
}
}
},
{
$project: {
_id: 0,
date: 1,
today: 1,
nextDay: 1,
visits: {
$reduce: {
input: "$visits",
initialValue: 0,
in: {
$add: [
"$$value",
{
$cond: [
{
$or: [
{
$and: [
{
$gte: [
"$$this.from",
"$date"
]
},
{
$lte: [
"$$this.from",
"$nextHour"
]
}
]
},
{
$and: [
{
$lte: [
"$date",
"$$this.to"
]
},
{
$gte: [
"$date",
"$$this.from"
]
}
]
}
]
},
1,
0
]
}
]
}
}
}
}
},
{
$match: {
$expr: {
$and: [
{
$gte: [
"$date",
"$today"
]
},
{
$lte: [
"$date",
"$nextDay"
]
},
]
}
}
},
{
$group: {
_id: "$date",
visits: {
$sum: "$visits"
}
}
},
{
$sort: {
_id: 1
}
}
])
MongoPlayground
For JS setup, click here
You may try this solution if you want more elegant way

Mongoose: $sum if conditions

I have a query with find and aggregate that find in Schedule model, and calculate the sum of total of services and the sum of total value of services. But I need to make this sum (sum of totalServices) with a condition, where the ´status´ equal to 2. can i make this?
My query:
Schedule.find(findTerm)
.skip(req.body.page * req.body.limit)
.limit(Number(req.body.limit))
.select(
"service.name value scheduleStart scheduleEnd comissionValue status paymentMethod"
)
.exec((err, response) => {
if (err) res.status(500).send(err);
Schedule.find(findTerm)
.count()
.exec((error, count) => {
if (error)
res.status(500).send({
error,
code: 0,
message: "Erro."
});
Schedule.aggregate([{
$match: {
store: req.body.store,
}
},
{
$group: {
_id: {
id: "$store"
},
totalValue: {
$sum: "$value"
},
totalServices: {
$sum: {
$cond: [ {
$eq: [ "$status", 2 ]
}]
}
},
count: {
$sum: 1
}
}
}...
Result of my query:
...{
"service": {
"name": "CABELO + BARBA"
},
"comissionValue": 0,
"paymentMethod": 0,
"_id": "5bfec336c6f00d2e88f8d765",
"scheduleStart": "2018-11-28 14:35",
"scheduleEnd": "2018-11-28 15:45",
"status": 2,
"value": 75
},
{
"service": {
"name": "Barba"
},
"comissionValue": 0,
"paymentMethod": 0,
"_id": "5bfec3ffc6f00d2e88f8d766",
"scheduleStart": "2018-11-28 18:30",
"scheduleEnd": "2018-11-28 18:50",
"status": 2,
"value": 20
}
],
"count": 4299,
"group": [
{
"_id": {
"id": "5b16cceb56a44e2f6cd0324b"
},
"totalValue": 777780048281, //right value
"totalServices": 945, //wrong value
"count": 676
}
]
}
I need to filter the sum of totalServices to only objects if the status equal to 2 (I tried to use $cond but not worked).
You need to check the status conditionally($cond) i.e. if status is equal ($eq) to 2 then $sum the value field else pass 0
Schedule.aggregate([
{ "$match": { "store": req.body.store }},
{ "$group": {
"_id": { "id": "$store" },
"totalValue": { "$sum": "$value" },
"totalServices": {
"$sum": { "$cond": [{ "$eq": ["$status", 2] }, 1, 0] }
},
"count": { "$sum": 1 }
}}
])

Getting only last field in mongoose $in Operator

I am trying to find all person records that time between start and end and have place id in [1,2] This is my query:
Person.find({
time: {
$gte: start,
$lt: end
},
"place.place_id": { $in: [1,2] }
}, {
"time":1,
"place.$": 1
},
function (err, docs) {
if (err) {
res.status(500).send(err);
} else {
res.status(200).send(docs);
}
}
);
But I am getting only one place. This is my output
[
{
"_id": "5bffc1e9bd35e42020c05cf1",
"time": "2018-11-29T10:38:01.401Z",
"places": [
{
"_id": "5bffc1e9bd35e42020c05de3",
"place_id": 1,
"place_name": "test1"
}
]
},
{
"_id": "5bffc256bd35e42020c05de4",
"time": "2018-11-29T10:40:01.324Z",
"places": [
{
"_id": "5bffc256bd35e42020c05ed6",
"place_id": 1,
"place_name": "test1",
}
]
}
]
I am getting only place which have id 1 my desired output is
[
{
"_id": "5bffc1e9bd35e42020c05cf1",
"time": "2018-11-29T10:38:01.401Z",
"places": [
{
"_id": "5bffc1e9bd35e42020c05de3",
"place_id": 1,
"place_name": "test1"
},
{
"_id": "5bffc1e9bd35e42020c05de3",
"place_id": 2,
"place_name": "test2"
}
]
},
{
"_id": "5bffc256bd35e42020c05de4",
"time": "2018-11-29T10:40:01.324Z",
"places": [
{
"_id": "5bffc256bd35e42020c05ed6",
"place_id": 1,
"place_name": "test1",
},
{
"_id": "5bffc256bd35e42020c05ed6",
"place_id": 2,
"place_name": "test2",
}
]
}
]
Update
Tried this but this gives me empty object
Persons.find({
time: {
$gte: start,
$lt: end
},
places: {
$filter: {
input: "$places",
as: "place",
cond: { $in: ["$$place.place_id",[ 1,2 ]] }
}
}
},
function (err, docs) {
if (err) {
res.status(500).send(err);
} else {
res.status(200).send(docs);
}
}
);
};
What am I doing wrong?? I also tried $elemMatch, $all Operator but same output.
How can I get all these places?
Thanks.
Update2
I am entrying data after every 2 minutes this is start and end values
var end = new Date();
var start = new Date(end);
start.setMinutes(end.getMinutes() - 10);
You need to use aggregation for this. Something like
db.collection.aggregate([
{ "$match": {
"time": { "$gte": start, "$lt": end },
"place.place_id": { "$in": [1, 2] }
}},
{ "$project": {
"time": 1,
"places": {
"$filter": {
"input": "$places",
"as": "place",
"cond": { "$in": ["$$place.place_id", [1, 2]] }
}
}
}}
])

how to write the single aggregation in the query

I am writing the first query greater than equal to CREATE_DATE and less then CREATE_DATE I got the answer ofter calculate the duration write another query but how to write the single query in the aggregation.
Query 1
db.lights.aggregate({
$match: {
CREATE_DATE: {
$gte: ISODate("2018-01-24T20:05:30.000Z"),
$lt: ISODate("2018-02-24T20:05:30.000Z")
}
}
});
Result
{
"_id": ObjectId("5a9a74843711955836a8b4b5"),
"SWITCHID": "Z4-W40-SS451A/4",
"CREATE_DATE": ISODate("2018-01-24T20:05:30Z"),
"RECEIVEDDATE": ISODate("2018-02-24T20:05:45Z"),
"STATUS": "LIGHTS ON"
}
Query 2
db.lights.aggregate([
{
$addFields: {
offduration: {
$divide: [
{
$subtract: [
"$RECEIVEDDATE",
"$CREATE_DATE"
]
},
3600000
]
}
}
}
]);
Result
{
"_id": ObjectId("5a9a75af3711955836a8b4c8"),
"SWITCHID": "Z4-W40-SS451A/5",
"CREATE_DATE": ISODate("2018-02-24T20:05:30Z"),
"RECEIVEDDATE": ISODate("2018-02-24T20:05:45Z"),
"STATUS": "LIGHTS ON",
"offduration": 0.004166666666666667
}
You need to improve your query like this :
db.lights.aggregate({
$match: {
CREATE_DATE: {
$gte: ISODate("2018-01-24T20:05:30.000Z"),
$lt: ISODate("2018-02-24T20:05:30.000Z")
}
}
},
{
$addFields: {
offduration: {
$divide: [
{
$subtract: [
"$RECEIVEDDATE",
"$CREATE_DATE"
]
},
3600000
]
}
}
},
{
$project: {
_id: 1,
SWITCHID: 1,
CREATE_DATE: 1,
RECEIVEDDATE: 1,
STATUS: 1,
offduration: '$offduration',
avgduration: { $avg: "$offduration" }
}
});

Categories

Resources