How to get count and max date with single query in MongoDB? - node.js

I have Post collection as like as following:
{ "_id" : ObjectId(..), "date" : ISODate("2014-03-01T08:00:00Z") }
{ "_id" : ObjectId(..), "date" : ISODate("2014-03-01T09:00:00Z") }
{ "_id" : ObjectId(..), "date" : ISODate("2014-03-15T09:00:00Z") }
{ "_id" : ObjectId(..), "date" : ISODate("2014-04-04T11:21:39.736Z") }
{ "_id" : ObjectId(..), "date" : ISODate("2014-04-04T21:23:13.331Z") }
I need to get total count and max date of post. So desired result for coeumtns above is the following:
{count: 5, date: ISODate("2014-04-04T21:23:13.331Z")}
How to get desired result with single query to MongoDB without handling and counting in application code?
EDIT: #chridam thanks for the response. I've accepted your answer as best one! Could help me with one more thing?
Let's say that posts are not exists yet, so I need to fetch result with zero count and current date as timestamp like the following:
{count: 0, [Date.now()]}
Is it possible with MongoDB ?

Use the $max and $sum operators as
Model.aggregate([
{
"$group": {
"_id": null,
"count": { "$sum": 1 },
"date": { "$max": "$date" }
}
}
]).exec(function (err, result) {
console.log(result);
})
EDIT: Addressing your further question with regards to an empty collection, the aggregate function will return an empty cursor since there wont be any documents to aggregate in the collection. So you would need to address this logic on the client i.e. check the results from the above aggregation, if the result is an empty array then create the placeholder doc as required:
Model.aggregate([
{
"$group": {
"_id": null,
"count": { "$sum": 1 },
"date": { "$max": "$date" }
}
}
]).exec(function (err, result) {
console.log(result);
if (!result.length) {
result = [{ count:0, date: new Date() }];
}
});

Related

Get 'n' number of documents started from specific document in MongoDB collection

Is this possible to get n number of documents from the collection, and fetching should start from specific object. e.g. if a collection has 100 documents then I need 10 documents started from 46 [ specific id ]. i.e. 46-55
return new Promise((resolve, reject) => {
return products_model.find({'from_id' : 9677270841774}).limit(limit).exec((err, records) => {
if(err)
reject(err)
else
resolve(records)
})
})
Edit: The original document structure is as follows:
{
"_id" : ObjectId("6128a0d9cf34c208c30e6800"),
"id" : 3238425384636,
"title" : "i3jMH8CHPWY6Ru18KrmsDGdiyl2qDuFjxXD1M4yCzJHrOmSF8v",
"body_html" : "This is body",
"vendor" : NumberInt(1),
"__v" : NumberInt(0)
},
{
"_id" : ObjectId("6128a0d9cf34c208c30e6805"),
"id" : 30336405569734,
},
{},
{},
{},
{}
and the API is
http://localhost:3000/products.json?since_id=30336405569734&limit=10
Example with 10ids, we want to get 4 ids starting from 4
filter id>=4
sort asceding id
limit 2
For you data you want read the query string and then you want, id>=30336405569734 and limit 10 .(numbers not strings)
Test code here
You can use the aggregation in mongoose.
db.collection.aggregate([
{
"$match": {
"id": {
"$gte": 4 // "$gte": 30336405569734 (for your data)
}
}
},
{
"$sort": {
"id": 1
}
},
{
"$limit": 2 // "$limit": 10 (for your data)
}
])
Or write a find in mongoose, i don't use mongoose but i think it will be like this.
products_model.find({ id: { $gte: 30336405569734 } })
.sort({id: 1})
.limit(10)
.then(products => {
console.log(products)
});
or
await products_model.find({ id: { $gte: 30336405569734 } })
.sort({id: 1})
.limit(10));

Sort From Nested Object Array MongoDB - NodeJS

hope you are doing well and Happy New Year to everybody. Hope it will end up better than 2020 X). I am facing an issue here that I can not find a solution so far. Here is a data MongoDB model example :
`{ "_id" : ObjectId("5fe715bf43c3ca0009503f1d"),
"firstname" : "Nicolas ",
"lastname" : "Mazzoleni",
"email" : "nicolas.mazzoleni#orange.fr",
"items" : [
{
"item" : ObjectId("5f6c5422eeaa1364b0d7b267"),
"createdAt" : "2020-12-26T10:51:43.685Z"
},
{
"item" : ObjectId("5f6c5422eeaa1364b0d7b270"),
"createdAt" : "2021-01-04T09:21:46.260Z"
}
],
"createdAt" : ISODate("2020-12-26T10:51:43.686Z"),
"updatedAt" : ISODate("2021-01-04T09:21:46.260Z"),
"__v" : 0
}`
Let's assume that I have a lot of rows like this one above, with different 'items.createdAt' dates.
At the end, the goal is to do a query that would find an item where the ObjectId is equal to what I am looking for, and sort by the matching 'items.createdAt' date. Here is the query I use at the moment that is not sorting by the matching nested object :
`const items = await repo
.find({ 'items.item': ObjectId("5f6c5422eeaa1364b0d7b270") })
.sort({ 'items.createdAt': -1 })
.limit(5)`
PS : I also tried to use aggregations that ended up with this query
`const items = await repo.aggregate([
{ $unwind: '$items' },
{ $match: { 'items.item': ObjectId("5f6c5422eeaa1364b0d7b270") } },
{ $sort: { 'items.createdAt': -1 } },
{ $limit: 5 },
])`
This one is working like a charm, but it does not use indexes, and I am searching into millions of records, which makes this query way to long to execute.
Thank you for all your help ! Nicolas
Unfortunately, MongoDB can't sort nested array by simple Query. You need to use aggregate for that.
db.collection.aggregate([
{
"$match": {
"items.item": ObjectId("5f6c5422eeaa1364b0d7b270")
}
},
{
"$unwind": "$items"
},
{
"$sort": {
"items.createdAt": -1
}
},
{ "$limit" : 5 },
{
"$group": {
"items": {
"$push": "$items"
},
"_id": 1
}
])

Mongoose: can't query array of dates

I have a test document like that:
{
"_id" : ObjectId("5fb6b0ed9cad6e97cfc24c2d"),
"dates" : [
{
"date" : ISODate("2020-01-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-02-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-03-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-04-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-05-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-06-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-07-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-08-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-09-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-10-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-11-01T00:00:00.000Z")
},
{
"date" : ISODate("2020-12-01T00:00:00.000Z")
}
]
}
Now I want to retrieve only dates $gt: 2020-14-01T00:00:00.000Z. I tried a lot of combinations but none of them worked for me.
This is one of the queries I tried (taken from Mongodb docs):
db.getCollection('things').find({_id: ObjectId("5fb6b0ed9cad6e97cfc24c2d"), "dates.date": { $gt: new Date("2020-04-01T00:00:00.000Z")} } )
But it return all the document, not the gt... i tried using new Date and new ISODate too, but same effect.
Thank you.
First of all, according to mongo documentation dates are in the format "<YYYY-mm-dd>".
Also more formats are allowed, but If you try to use 2020-14-01 as date it will fail (unless you convert string to date with an specific format) because month is 14.
But, answering the question, you need a query like this:
EDITED
db.collection.aggregate({
"$match": {
"_id": ObjectId("5fb6b0ed9cad6e97cfc24c2d"),
}
},
{
"$project": {
"dates": {
"$filter": {
"input": "$dates",
"as": "item",
"cond": {
"$gt": [
"$$item.date",
ISODate("2020-01-14T00:00:00.000Z")
]
}
}
}
}
})
First $match by _id to get only the document you want. And then using $project to create the fields you want to get. You can filter in the array those values whose field date is greater than your date using $filter and $gt
Note that I've used 2020-01-14 to avoid errors.
Example here.
Also another example using $dateFromString in this query.
Edit: You can use $dateFromString and specify a format. Check this example

MongoDB query to get the sum of all document's array field length

Below is the sample document of a collection, say "CollectionA"
{
"_id" : ObjectId("5ec3f19225701c4f7ab11a5f"),
"workshop" : ObjectId("5ebd37a3d33055331eb4730f"),
"participant" : ObjectId("5ebd382dd33055331eb47310"),
"status" : "analyzed",
"createdBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"updatedBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"results" : [
{
"analyze_by" : {
"user_name" : "m",
"user_id" : "5eb7aa24d33055331eb4728c"
},
"category_list" : [
"Communication",
"Controlling",
"Leading",
"Organizing",
"Planning",
"Staffing"
],
"analyzed_date" : ISODate("2020-05-19T14:48:49.993Z"),
}
],
"summary" : [],
"isDeleted" : false,
"isActive" : true,
"updatedDate" : ISODate("2020-05-19T14:48:50.827Z"),
"createdDate" : ISODate("2020-05-19T14:47:46.374Z"),
"__v" : 0
}
I need to query all the documents to get the "results" array length and return a sum of all document's "results" length.
For example,
document 1 has "results" length - 5
document 2 has "results" length - 6
then output should be 11.
Can we write a query, instead of getting all, iterating and the adding the results length??
If I had understand clearly you would like to project the length of the result attribute.
So you should check the $size operator would work for you.
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
You can use $group and $sum to calculate the total size of a field which contains the size of your results array. To create the field, You can use $size in $addFields to calculate the size of results in each document and put it the field. As below:
db.getCollection('your_collection').aggregate([
{
$addFields: {
result_length: { $size: "$results"}
}
},
{
$group: {
_id: '',
total_result_length: { $sum: '$result_length' }
}
}
])
You use an aggregation grouping query with $sum and $size aggregation operators to get the total sum of array elements size for all documents in the collection.
db.collection.aggregate( [
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
] )
Aggregation using Mongoose's Model.aggregate():
SomeModel.aggregate([
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
]).
then(function (result) {
console.log(result);
});

Native NodeJS MongoDB driver returns different result than direct query

I have a Node/Express backend using the native mongodb driver to handle data. I have the following function that extracts data from my mongodb database:
else if (year && grouping == 2){
collection.aggregate([
{ $match: {postdate: { $gte : new Date(yearstart), $lte : new Date(yearend)},factor:1 }},
{ $group: { _id: {"category":"$category","month":{ "$month": "$postdate" }} , "total": { $sum: "$debit" } } },
{ $project: { "_id":0, "category":"$_id.category", "month":"$_id.month", "total":1 } }
]
).toArray(function(err, items) {
console.log("get transactions by year and month category grouping result: ", items);
res.json(GroupByMonthCat(items));
}
The value of yearstart is: '2018-01-01' and the value of yearend is '2018-12-01'. So in my query I am grouping the matched records on the month part of postdate.
However, the result I get is the following for (category: Bills and month:1):
{
"subtotal" : 475,
"category" : "Bills",
"month" : 1
}
When I run the same query outside my app directly against my mongodb:
db.transtest.aggregate(
[
{ $match: {category: "Bills" ,postdate: { $gte : new Date("2018-01-01"), $lte : new Date("2018-01-31")},factor:1 }},
{ $group: { _id: {"category":"$category","month":{ "$month": "$postdate" }} , "subtotal": { $sum: "$debit" } } },
{ $project: { "_id":0, "category":"$_id.category", "month":"$_id.month", "subtotal":1} },
{ $sort : { category : -1, month: 1 } }
]
);
I get the following result:
{
"subtotal" : 183.9,
"category" : "Bills",
"month" : 1
}
Why would this be happening? There are only two results in my database for the month of January 2018, hence my expected subtotal of 183.9 as shown when I run the same exact query directly against my database:
{
"_id" : ObjectId("5a7641b0d4b6828ad37c99a1"),
"transid" : 5500,
"postdate" : ISODate("2018-01-16T05:00:00.000Z"),
"category" : "Bills",
"debit" : 53.9
},
{
"_id" : ObjectId("5a7641b0d4b6828ad37c99a5"),
"transid" : 5504,
"postdate" : ISODate("2018-01-25T05:00:00.000Z"),
"category" : "Bills",
"debit" : 130
}

Resources