Mongoose, mongodb, bad aggregate grouping - node.js

I m actually trying to get some information concerning my app.
I have to get some informatiogrouping by day / month / year. I have the good result attributes, but information is displayed even if there is nothing in DB.
NB : Start and End are good formatted dates.
TraitementNettoyage.aggregate([
{$match: { 'dateEntre': {$gt: start}, 'dateEntre': {$lt: end} }},
{$group: {'_id': {'day': {'$dayOfMonth': '$dateEntre'}, 'month': {'$month': '$dateEntre'}, 'year': {'$year': '$dateEntre'}}, count: {$sum: 1}}}
]).exec((err, res)=>
console.log res
)
And I get this resultset :
[
{
"_id": {
"day": 24,
"month": 3,
"year": 2015
},
"count": 2
}
]
The fact is that I have nothing in DB concerning the 2015-03-24.
In my DB, I have only 2 sets of data with the 2015-03-23 date.
What should I correct in my request to get the exact two resultsets :
[
{
"_id": {
"day": 24,
"month": 3,
"year": 2015
},
"count": 0
}
]
and
[
{
"_id": {
"day": 23,
"month": 3,
"year": 2015
},
"count": 2
}
]
?
EDIT :
Here the resultset with $lte and $gte :
[
{
"_id": {
"day": 25,
"month": 3,
"year": 2015
},
"count": 2
},
{
"_id": {
"day": 24,
"month": 3,
"year": 2015
},
"count": 2
}
]
The problem is that the count are not correct. in fact it should be 2 for 24/03/2015 and 0 for 25/03/2015.
Thanks for advance

split the $match for dateEntre
TraitementNettoyage.aggregate([
{$match: { 'dateEntre': {$gte: start}}},
{$match: { 'dateEntre': {$lte: end}}},
{$group: {'_id': {'day': {'$dayOfMonth': '$dateEntre'}, 'month': {'$month': '$dateEntre'}, 'year': {'$year': '$dateEntre'}}, count: {$sum: 1}}}
]).exec((err, res)=>
console.log res
)

Related

mongoose group by every hour and fill empty hours with null

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 ?

Mongodb aggregation to pass both a matched array and an unmatched array

I've got a MongoDB / Nodes aggregation that looks a little like this (there are other values in there, but this is the basic idea).
[
{
'$unwind': {
'path': '$Vehicles'
}
},
{
'$match': {
'Vehicles.Manufacturer': 'FORD'
}
},
{
'$facet': {
'makes': [
{
'$group': {
'_id': '$Vehicles.Manufacturer',
'count': {
'$sum': 1
}
}
}
]
}
},
{
'$project': {
'makes': {
'$sortArray': {
'input': '$makes',
'sortBy': 1
}
}
}
}
]
This works fine. But I would also like to pass an unmatched list through. IE an an array of vehicles whose Manufacturer = FORD and an other list of all Manufacturer.
Can't get it to work. Any ideas please?
Thanks in advance.
Edit:-
The current output looks like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
]
}]
and ideally it would look something like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
],
"unfiltered_makes": [
{
"_id": "ABARTH",
"count": 1
},
{
"_id": "AUDI",
"count": 7
},
{
"_id": "BMW",
"count": 2
},
{
"_id": "CITROEN",
"count": 4
},
{
"_id": "DS",
"count": 1
},
{
"_id": "FIAT",
"count": 1
}.... etc
]
}]
The data looks a bit like this:
"Vehicles": [
{
"Id": 1404908,
"Manufacturer": "MG",
"Model": "3",
"Price": 11995 .... etc
},{
"Id": 1404909,
"Manufacturer": "FORD",
"ManufacturerId": 34,
"Model": "Focus",
"Price": 12000 .... etc
} ... etc
]
In this case you can do something like:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$facet: {
makes: [{$match: {_id: "FORD"}}],
unfiltered_makes: [{$group: {_id: 0, data: {$push: "$$ROOT"}}}]
}
},
{$project: {makes: 1, unfiltered_makes: "$unfiltered_makes.data"}}
])
See how it works on the playground example
Another option is:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$group: {
_id: 0,
unfiltered_makes: {$push: "$$ROOT"},
makes: {$push: {$cond: [{$eq: ["$_id", "FORD"]}, "$$ROOT", "$$REMOVE"]}}
}
}
])
See how it works on the playground example
Here's another way to do it using "$function" to generate a histogram of "Manufacturer" and format the returned array. The javascript function only traverses the "Vehicles" array once, so this may be fairly efficient, although I did not do algorithm timing comparisons on a large collection.
N.B.: I'm a javascript noob and there may be a better way to do this.
db.collection.aggregate([
{
"$set": {
"unfiltered_makes": {
"$function": {
// generate histogram of manufacturers and format output
"body": "function(makes) {const m = new Object();makes.forEach((elem) => {m[elem.Manufacturer] = m[elem.Manufacturer] + 1 || 1});return Object.entries(m).map(([make, count]) => {return {'_id':make, 'count':count}})}",
"args": ["$Vehicles"],
"lang": "js"
}
}
}
},
{
"$project": {
"_id": 0,
"unfiltered_makes": 1,
"makes": {
"$filter": {
"input": "$unfiltered_makes",
"as": "make",
"cond": {
"$eq": [
"$$make._id",
// your search "Manufacturer" goes here
"FORD"
]
}
}
}
}
}
])
Try it on mongoplayground.net.

Mongodb Join on _id field from to ObjectId

I have two collections
Reports
{"brand": "Nike", "created": "2021-05-03T20:12:32.911000", "id": ObjectId("60929848088f18212809221c")}
Status
{"id": "60929848088f18212809221c", "report": ObjectId("60929848088f18212809221c"), "created": "2021-05-05T20:12:32.911000"}
I want to join these collections based on the report(in Status collection) - _id ( in Reports collection).
I tried the below query(The code here is complex because it does a few more things that are necessary to me):
query = [
{'$group': {'_id': '$_id', 'data': {'$last': '$$ROOT'}}},
{'$match': match},
{'$sort': {'data.created': pymongo.DESCENDING}},
{'$facet': {
'stage1': [{'$group': {'_id': None, 'count': {'$sum': 1}}}],
'stage2': [{'$skip': offset}, {'$limit': limit}]
}},
{'$unwind': '$stage1'},
{'$project': {
'count': '$stage1.count',
'data': '$stage2'
}},
{
"$lookup": {
"from": "status",
"localField": "data._id",
"foreignField": "report",
"as": "report"
}
}
]
I expect such a result:
[
{
"count": 3,
"data": [
{
"_id": ObjectId("5a934e000102030405000001"),
"data": {
"_id": ObjectId("5a934e000102030405000001"),
"brand": "Nike",
"created": "2021-05-03T20:12:32.911000",
},
"report": {
"_id": ObjectId("5a934e000102030405000003"),
"created": "2021-05-05T20:12:32.911000",
"id": "60929848088f18212809221c",
"report": ObjectId("5a934e000102030405000001")
},
},
}
]
}
]
I mean I mean it will be in every result
I tried to get help with that too but it did not work Here.
I'm using Mongo DB 4+

Querying with mongoose/mongoDB on nested document

I have a car model given as below
{
"_id": "54b8a71843286774060b8bed",
"name": "Car1",
"active": true,
"model": [
{
"name": "Model1",
"active": true,
"_id": "54b8a71843286774060b8bee",
"available": [
{
"Day": "Mon",
"quantity": "6"
},
{
"Day": "Tue",
"quantity": "6"
},
{
"Day": "Wed",
"quantity": "6"
},
{
"Day": "Thurs",
"quantity": "6"
},
{
"Day": "Fri",
"quantity": "0"
}
]
},
{
"name": "Model2",
"active": true,
"_id": "54b8a71843286774060b8bef",
"available": [
{
"Day": "Mon",
"quantity": "6"
},
{
"Day": "Tue",
"quantity": "6"
},
{
"Day": "Wed",
"quantity": "6"
},
{
"Day": "Thurs",
"quantity": "6"
},
{
"Day": "Fri",
"quantity": "6"
}
]
},
{
"name": "Model3",
"active": true,
"_id": "54b8a71843286774060b8beg",
"available": [
{
"Day": "Mon",
"quantity": "6"
},
{
"Day": "Tue",
"quantity": "6"
},
{
"Day": "Wed",
"quantity": "6"
},
{
"Day": "Thurs",
"quantity": "6"
},
{
"Day": "Fri",
"quantity": "0"
}
]
}
]
}
I am trying to search availability of car on given days.
Like if I select Friday then it should return me cars whose quantity more than 0 on Friday but currently it is returning all the cars having quantity 0 as well.
I have written query as below
Car.find({
'active': true,
'model.available': {
$elemMatch: {
quantity: {$gte : 1}
}
}
})
But it returning documents those are having quantity 0 also.
For this, you'll need the aggregation pipeline.
The following code snippet does this:
Find all documents with at least one matching model.
Split up the documents: a document with an array of 3 models in it gets turned into three documents with one model each:
{name: "Car1": 1, models: [{name: "Model1"}, {name: "Model2"}, {name: "Model3"}]}
Becomes:
{name: "Car1", models: {name: "Model1"}} & {name: "Car1", models: {name: "Model2"}} & {name: "Car1", models: {name: "Model3"}}.
The split up documents are filtered (again) on quantity and day.
Optionally, glue the documents back together again. You might not need that in your application.
db.cars.aggregate([
// First, we do your query, which will return
// every document that has _at least one_
// model that is available.
{$match: {
'active': true,
'model.available': {$elemMatch: {
'quantity': {$gte: 1},
'Day': 'Fri'
}}
}},
// We split up the found documents,
// every document will now have exactly
// one 'model' in it.
{$unwind: "$model"},
// We now filter the split documents.
{$match: {
'model.available': {$elemMatch: {
'quantity': {$gte: 1},
'Day': 'Fri'
}}
}},
// If you want, you can now glue the
// models back together again.
{$group: {
_id: "$_id", // Group all documents with the same _id
name: {$first: "$name"},
active: {$first: "$active"},
model: {$push: "$model"} // Make an array of models
}}
])
Important note: For $gte to work, you'll need to store your quantity as a Number, not a String. Since your example has the numbers stored as strings, you might want to double check them in your database.

groups by month and year using mongoose.js

my collection in mongodb looks like below:
{
"AccountID" : "87f7fd60-d1ad-11e2-98bb-795730bce125",
"userId" : ObjectId("51b59fbec46916e60d00000c"),
"_id" : ObjectId("51b6e603e3efef161b000003"),
"accessDate" : ISODate("2013-06-11T08:55:31.957Z"),
"__v" : 0
}
{
"AccountID" : "47f7fd60-d1ad-11e2-98bb-795730bce125",
"userId" : ObjectId("51b59fbec46916e60d00000d"),
"_id" : ObjectId("51b6e603e3efef161b000003"),
"accessDate" : ISODate("2013-05-1T08:05:31.957Z"),
"__v" : 0
}
i what to write a query which results the below result:
this is result as grouped by month and year and the count per day.
{
"usage": [
{
"year": 2013,
"monthlyusage": [
{
"month": 1,
"dailyusage": [
{
"day": 1,
"count": 205
},
{
"day": 2,
"count": 1109
},
{
"day": 4,
"count": 455
}
]
},
{
"month": 2,
"dailyusage": [
{
"day": 11,
"count": 256
},
{
"day": 2,
"count": 1001
},
{
"day": 5,
"count": 65
}
]
}
]
},
{
"year": 2012,
"monthlyusage": [
{
"month": 12,
"dailyusage": [
{
"day": 1,
"count": 78
},
{
"day": 2,
"count": 7009
},
{
"day": 28,
"count": 55
}
]
},
{
"month": 11,
"dailyusage": [
{
"day": 11,
"count": 800
},
{
"day": 2,
"count": 5094
},
{
"day": 25,
"count": 165
}
]
}
]
}
]
}
How can i do this using mongoose.js framework
Mongoose provides a lightweight wrapper around the MongoDB aggregation framework. If you're new to aggregation, you can learn more about in from the MongoDB docs: http://docs.mongodb.org/manual/aggregation/
To massage your data into the form you've described above, you can use an aggregation pipeline with a series of $group operations. Here it is using the mongoose framework:
var dateSchema = mongoose.Schema({…});
var DateItem = mongoose.model('DateItem', dateSchema);
DateItem.aggregate(
{ $group : {
_id : { year: { $year : "$accessDate" }, month: { $month : "$accessDate" },day: { $dayOfMonth : "$accessDate" }},
count : { $sum : 1 }}
},
{ $group : {
_id : { year: "$_id.year", month: "$_id.month" },
dailyusage: { $push: { day: "$_id.day", count: "$count" }}}
},
{ $group : {
_id : { year: "$_id.year" },
monthlyusage: { $push: { month: "$_id.month", dailyusage: "$dailyusage" }}}
},
function (err, res)
{ if (err) ; // TODO handle error
console.log(res);
});
});
The first $group will result in documents of this form, one for each day:
{
"_id" : { "year" : 2013, "month" : 8, "day" : 15 },
"count" : 1
}
The second $group will result in documents grouped by month:
{
"_id" : { "year" : 2012, "month" : 11 },
"dailyusage" : [
{ "day" : 6, "count" : 1 },
{ "day" : 9, "count" : 1 },
... ]
},
And the third $group will result in even larger documents, one for each year.
This query will aggregate your data into large, hierarchical documents. If you plan to run queries on this data after aggregation, however, this might not be the most useful form for your data to be in. Consider how you'll be using the aggregated data. A schema involving more smaller documents, perhaps one per month or even one per day, might be more convenient.

Resources