check date existence on array of string - javascript

This is my dataset from MongoDB Collection:
{
"_id": "60c0ace96e93993880efd337",
"lv": [
uid: 1,
"createdate": "2021-12-15T12:30:01.935Z",
"updatedAt": [
"2021-12-15T12:31:11.635Z",
"2021-12-15T12:31:51.955Z",
"2021-12-16T12:30:01.935Z",
"2021-12-16T12:30:01.935Z",
"2021-12-17T12:30:01.935Z",
"2021-12-18T12:30:01.935Z"
]
]
},
{
...
}
I want to filterout only the data which date range lies in createdate column or updatedAt column.
I am not able to get the desired result. Not getting the idea that where I am making the mistake in the query or coed.
What I have tried I will mention here.
let startA = new Date("2021-12-14");
const a = new Date(startA.setHours(startA.getHours() - 5));
const start = new Date(a.setMinutes(startA.getMinutes() - 30));
let endA = new Date("2021-12-17");
const b = new Date(endA.setHours(endA.getHours() - 5));
const end = new Date(b.setMinutes(endA.getMinutes() - 30));
const fetchData = await MyCollection.findOne(
{
_id: ObjectId(req.body.id),
'lv.createdate': { $gte: start, $lt: end },
'lv.updatedAt': {
$elemMatch: { $gte: start, $lt: end }
}
}
).lean().exec();
Any help or suggestion is really appreciated. Thanks in advance for the interaction.

Your input data is not valid. With some assumptions, the solution could be this one:
db.MyCollection.aggregate([
{
$set: {
lv: {
$map: {
input: "$lv",
as: "lv",
in: {
$filter: {
input: "$$lv.updatedAt",
cond: {
$and: [
{ $gte: ["$$this", start] },
{ $lt: ["$$this", end] }
]
}
}
}
}
}
}
}
])
or this one:
db.MyCollection.aggregate([
{
$set: {
"lv.updatedAt": {
$filter: {
input: "$lv.updatedAt",
cond: {
$and: [
{ $gte: ["$$this", start] },
{ $lt: ["$$this", end] }
]
}
}
}
}
}
])

Related

MongoDB Count By Current Month

I'm new to MongoDB. I need to get the count of the posts which are posted in this current month. PlateModel is my model I've kept timestamps true also. It looks like the below:
{ timestamps: true }
My present code in the Controller looks like below:
const today = new Date();
const host_count = await PlateModel.find({
$and: [
{
postedBy: req.user._id,
},
{
createdAt: today.getMonth(),
},
],
}).count();
But, I get the count value 0. Can anyone help me to figure out the problem?
You could filter the objects from the start of the current month to the end of the next month:
const startOfCurrentMonth = new Date();
startOfCurrentMonth.setDate(1);
const startOfNextMonth = new Date();
startOfNextMonth.setDate(1);
startOfNextMonth.setMonth(startOfNextMonth.getMonth() + 1);
const host_count = await PlateModel.find({
$and: [
{
postedBy: req.user._id,
},
{
createdAt: {
$gte: startOfCurrentMonth,
$lt: startOfNextMonth
}
},
],
}).count();
You can use the Moment library.
const moment = require("moment");
const firstdate = moment().startOf('month').format('DD-MM-YYYY');
console.log(firstdate);
const lastdate=moment().endOf('month').format("DD-MM-YYYY");
console.log(lastdate);
const host_count = await PlateModel.find({
$and: [
{
postedBy: req.user._id,
},
{
createdAt: {
$gte:firstdate,
$lt:lastdate
}
},
],
}).count();

Return variable from database find instead of array

I need to pull two values out of my mongoDB database and right now the code is returning an array. How do I get:
totalGuests
attendedGuests
out of the query and stored in variables so I can display them on the client?
module.exports.showEvent = async(req, res,) => {
const event = await Event.findById(req.params.id).populate('artist');
if (!event) {
req.flash('error', 'Cannot find that Event');
return res.redirect('/events');
}
res.render('events/show', { event });
const { guest_id } = req.cookies;
const lookUp = Event.collection.find({ _id: req.params.id},
{
_id: 1,
name: 1,
guests: 1,
totalGuests: {
$size: "$guests"
},
attendedGuests: {
$size: {
"$filter": {
input: "$guests",
cond: {
$eq: [
"$$this.attended",
"Y"
]
}
}
}
}
});
console.log(lookUp);

Count number of fields that match in an Array of Object in mongodb

What I have
I have a DB in MongoDB like this:
{
"_id": {
"$oid": "60ba531acbfed3545c51a49e"
},
"email": "shaswat.dharaiya#gmail.com",
"Formats": [{
"format": "AST-QC",
}],
"Series": [{
"seq": "AST-QC - 1",
},
{
"seq": "AST-QC - 2",
},
{
"seq": "AST-QD - 1",
}]
}
I am successfully getting the data from the Formats array using this query:
const pipeline = [
{ $match: { "email": email } },
{ $unwind: "$Formats" },
]
const res = await colc.aggregate(pipeline)
What I want
Along with the data in the Formats array, I need the count of every format that is used by seq in Series array.
I am certain that it can be done using $addFields, Something like this.
const pipeline = [
{ $match: { "email": email } },
{ $unwind: "$Formats" },
{ $addFields: {"Formats.count": 0} }
]
const res = await colc.aggregate(pipeline)
But I am not sure as to how.
I don't want to call another query using .count()
$filter to iterate loop of Series array
$regexMatch to search format in seb
$size to get total elements in filtered result
const pipeline = [
{ $match: { email: email } },
{ $unwind: "$Formats" },
{
$addFields: {
"Formats.count": {
$size: {
$filter: {
input: "$Series",
cond: {
$regexMatch: {
input: "$$this.seq",
regex: "$Formats.format"
}
}
}
}
}
}
}
]
Playground

Building your MongoDB $match as a string dynamically

Im trying to build my $match dynamically for my MongoDB request, and when I do the simple stuff it works perfect, like this:
var matchStr = {};
matchStr.status = { "$lte": 3 };
matchStr.checkout = { $gte: Math.round(new Date(d).getTime()/1000) }
And then I run the
bookingTable.aggregate([
{
$match: {
$and: [ matchStr ]
}
}, etc....
Which gives a nice:
matchStr: {
"status": {
"$lte": 3
},
"checkout": {
"$gte": 1527669588
}
}
So thats all great, but what if I want to put something like this into the matchStr...
{ $or: [ { "managerDate": {$lte: managerLast} }, { "activityDate": {$lte: activityLast} } ] }
,
{ $or: [ { "expireDate": {$gt: oneDayBackward} }, { "status": {$lt: 9}} ] }
,
{ "status": { $in: [0, 1, 2, 9 ] } }
How can I do that?
There are multiple syntax for accessing the property of an object
var matchStr = {}
matchStr.status = { "$lte": 3 }
matchStr.checkout = { "$gte": Math.round(new Date().getTime()/1000) }
matchStr["$or"] = [
{ "managerDate": { "$lte": "managerLast" }},
{ "activityDate": { "$lte": "activityLast" }}
]
or If you want to push to $or operator
matchStr["$or"].push({ "managerDate": { "$lte": "managerLast" } })
matchStr["$or"].push({ "activityDate": { "$lte": "activityLast" } })

skipped count 0 in aggregate function

I'm stuck on this for couple of days. I'm trying to get the count: 0 where there is no documents in the given time period. This is the aggregate function I'm using at the moment:
var getCount = function(timeBlock, start, end, cb) {
Document.aggregate(
{
$match: {
time: {
$gte: new Date(start),
$lt: new Date(end)
}
}
},
{
$project: {
time: 1,
delta: { $subtract: [
new Date(end),
'$time'
]}
}
},
{
$project: {
time: 1,
delta: { $subtract: [
"$delta",
{ $mod: [
"$delta",
timeBlock
]}
]}
}
},
{
$group: {
_id: { $subtract: [
end,
"$delta"
]},
count: { $sum: 1 }
}
},
{
$project: {
time: "$_id",
count: 1,
_id: 0
}
},
{
$sort: {
time: 1
}
}, function(err, results) {
if (err) {
cb(err)
} else {
cb(null, results)
}
})
}
I tried using $cond, but with no luck
The group stage is producing documents based on grouping on your given _id and counting the number of documents from the previous stage that end up in the group. Hence, a count of zero would be the result of a document being created from 0 input documents belonging to the group. Thinking about it this way, it's clear that there's no way the aggregation pipeline can do this for you. It doesn't know what all of the "missing" time periods are and it can't invent the appropriate documents out of thin air. Reapplying your extra knowledge about the missing time periods to complete the picture at the end seems like a reasonable solution (not "hacky") if you need to have an explicit count of 0 for empty time periods.
Though it has already been said the best thing to do here is "merge" your results post process rather than expect "keys" that do not exist to appear or to issue multiple queries with explicit keys that are possibly not going to aggregate results and combine them.
What has not already been said is how you actually do this, so I'll give you a MongoDB "thinking" kind of way to collect your results.
As a quick disclaimer, you could possibly employ much the same approach by "seeding" empty keys for each interval using mapReduce, or possibly even altering your data so that there is always an empty value within each possible block. Those approaches seem basically "hacky" and in the mapReduce case is not going to provide the best performance or muliple results.
What I would suggest is that working with collection results for the MongoDB brain can be made simple. There is a neat little solution called neDB, which is billed as a kind of SQL Lite for MongoDB. It supports a subset of functionality and is therefore perfect for "in memory" manipulation of results with a MongoDB mindset:
var async = require('async'),
mongoose = require('mongoose'),
DataStore = require('nedb'),
Schema = mongoose.Schema;
var documentSchema = new Schema({
time: { type: Date, default: Date.now }
});
var Document = mongoose.model( "Document", documentSchema );
mongoose.connect('mongodb://localhost/test');
var getCount = function(timeBlock, start, end, callback) {
async.waterfall(
[
// Fill a blank series
function(callback) {
var db = new DataStore();
var current = start;
async.whilst(
function() { return current < end },
function(callback) {
var delta = end - current;
db.insert({ "_id": end - delta, "count": 0 },function(err,doc) {
//console.log( doc );
current += timeBlock;
callback(err);
});
},
function(err) {
callback(err,db);
}
);
},
// Get data and update
function(db,callback) {
var cursor = Document.collection.aggregate(
[
// Match documents
{ "$match": {
"time": {
"$gte": new Date(start),
"$lt": new Date(end)
}
}},
// Group. 1 step and less hacky
{ "$group": {
"_id": {
"$let": {
"vars": {
"delta": {
"$subtract": [
{ "$subtract": [ new Date(end), "$time" ] },
{ "$mod": [
{ "$subtract": [ new Date(end), "$time" ] },
timeBlock
]}
]
}
},
"in": { "$subtract": [ end, "$$delta" ] }
}
},
"count": { "$sum": 1 }
}}
],
{ "cursor": { "batchSize": 100 } }
);
cursor.on("data",function(item) {
cursor.pause();
console.log( "called" );
db.update(
{ "_id": item._id },
{ "$inc": { "count": item.count } },
{ "upsert": true },
function(err) {
cursor.resume();
}
);
});
cursor.on("end",function() {
console.log( "done" );
db.find({},function(err,result) {
callback(err,result);
});
});
}
],
function(err,result) {
callback(err,result);
}
);
}
mongoose.connection.on("open", function(err,conn) {
getCount(
1000 * 60 * 60, // each hour
new Date("2014-07-01").valueOf(), // start
new Date("2014-07-02").valueOf(), // end
function(err,result) {
if (err) throw err;
console.log( result );
}
);
});
So essentially create each interval as in memory collection and then just update those interval records with the actual data retrieved. I can't think of another way to do that where it would be more simple and natural to the way of thinking.
Just a footnote, the "interval" logic is just replicated from your question, but in fact the time periods are "rounded up" where 15 minutes would appear in hour 1. It usually is the practice to round down so that everything belongs to the interval it falls in and not the next one.
this is hacky fix I did for now:
var getCount = function(timeBlock, start, end, cb) {
Document.aggregate(
{
$match: {
time: {
$gte: new Date(start),
$lt: new Date(end)
}
}
},
{
$project: {
time: 1,
delta: { $subtract: [
new Date(end),
'$time'
]}
}
},
{
$project: {
time: 1,
delta: { $subtract: [
"$delta",
{ $mod: [
"$delta",
timeBlock
]}
]}
}
},
{
$group: {
_id: { $subtract: [
end,
"$delta"
]},
count: { $sum: 1 }
}
},
{
$project: {
time: "$_id",
count: 1,
_id: 0
}
},
{
$sort: {
time: 1
}
}, function(err, results) {
if (err) {
cb(err)
} else {
// really hacky way
var numOfTimeBlocks = ( end - start ) / timeBlock
// in case there is no 0s in the given period of time there is no need
// to iterate through all of the results
if ( results.length === numOfTimeBlocks ) {
cb(results);
} else {
var time = start;
var details = [];
var times = results.map(function(item) {
return item.time;
});
for( var i = 0; i < numOfTimeBlocks; i++) {
time += timeBlock;
var idx = times.indexOf(time);
if (idx > -1) {
details.push(results[idx]);
} else {
var documentCount = { count: 0, time: time };
details.push(documentCount);
}
}
cb(details);
}
}
})
}
I was also thinking about doing one query per time block, which gives the same result but I think is inefficient because you query the database N times.

Categories