How to find data by funcion in mongoose? - mongodb

I have a collection in mongodb using mongoose packages like below
[
{
_id: new ObjectId("62ae97b6be08b688f93f2c07"),
reportId: '1',
method: 'A1',
category: 'B2',
date: '2022-06-19',
time: '22:55',
emergency: 'normal',
__v: 0
},
{
_id: new ObjectId("62ae97b6be08b688f93f2c08"),
reportId: '2',
method: 'A3',
category: 'B5',
date: '2022-06-18',
time: '23:05',
emergency: 'normal',
__v: 0
},
{
_id: new ObjectId("62ae97b6be08b688f93f2c09"),
reportId: '3',
method: 'A5',
category: 'B1',
date: '2022-06-19',
time: '23:55',
emergency: 'urgent',
__v: 0
}
]
and I want to filter this data, and here is my find function()
const options = [
{ method: { $in: ['A1','A2'] } },
{ emergency: { $in: data.emergency } },
{ category: { $in: data.category } }
];
const response = await Report.find({ $or: options,});
Until now, it works perfectly, but I still got one more filter: the date and time (They are all int type String).
I want to search for the range date and time between last night after 23 o'clock to 23 o'clock tonight.
But I have no idea how to write the query, please help me figure it out, thanks!!!
Here is my testing query:
date: {
$where: function () {
const yesterday = moment().subtract(1, 'days').format('YYYYMMDD') + '2300';
const date = moment(this.date).format('YYYYMMDD') + this.time.replace(':', '');
const today = moment().format('YYYYMMDD') + '2300';
return yesterday < date && date <= today;
},
},

I would recommend you to first save dates in a format that supports comparators (lt, gt, etc.) such as a Timestamp or even a plain number. That way you can use MongoDB filters like this:
let filter = {
date: { $gt: new Date(..), $lt: new Date(..) }
}
// This can be executed directly in the Mongo shell.
{
created_on: {
$gt: ISODate("1972-12-12"),
$lt: ISODate('2022-06-019')
}
}
But if you insist on using strings, then you would need to filter all your documents, and then parse each one to do your comparison.

Related

how to add 7 days in date manually using mongodb(aggregation)?

I have a query in which I'm getting data using start Date and end Date. And it's working perfectly fine. now I want to add 7 days in my start date using aggregation and want to get data of start Date + 7 added days. i have tried the below code but couldn't get any success
{
$match: {
publishedAt: {
$lt: new Date(startDate),
$gte: new Date(endDate),
},
},
},
{
$set: {
publishedAt: {
$add: [new Date(startDate), 1000 * 60 * 60 * 24],
},
},
},
Use this:
{
$set: {
publishedAt: {
$dateAdd: {
startDate: new Date(startDate),
unit: "day",
amount: 7
}
}
}
}
Or use a 3rd party library, e.g. Luxon
{
$set: {
publishedAt: DateTime.now().plus({ days: 7 }).toJSDate()
}
}

Express, Mongodb (mongoose) - Data comparison and count

In my mongodb database i have datas like:
{ id: 'ran1', code: 'ABC1', createdAt: 'Sep 1 2022', count: 5 }
{ id: 'ran2', code: 'ABC1', createdAt: 'Sep 2 2022', count: 3 }
{ id: 'ran3', code: 'ABC2', createdAt: 'Sep 1 2022', count: 2 }
{ id: 'ran4', code: 'ABC1', createdAt: 'Oct 1 2022', count: 1 }
{ id: 'ran5', code: 'ABC1', createdAt: 'Oct 2 2022', count: 2 }
{ id: 'ran6', code: 'ABC2', createdAt: 'Ocr 1 2022', count: 1 }
now as an output i want all the data from October but i also want to count and compare the percentage.
So the output for October will be
{code: 'ABC1', totalCount: the sum of total count of oct (1+2) =3 , percent: (total count of oct - total count of sep)/total count of oct * 100 }
{code: 'ABC2', totalCount: 1, percent: -100}
I tried to achieve these output using two different aggregation and later map the current month aggregation with each element from previous month aggregation. But i think there are some better solution.
Here is my code
const { filterDate, shop } = req.query;
const splittedFilter = filterDate.split("-");
const query = {
shopUrl: { $regex: shop, $options: "i" },
createdAt: {
$gte: new Date(splittedFilter[0]),
$lte: new Date(splittedFilter[1]),
},
};
const currentCodes = await BlockedCode.aggregate([
{
$match: query,
},
{
$group: {
_id: "$discountCode",
totalCount: { $sum: "$count" },
},
},
]);
const prevQuery = {
shopUrl: { $regex: shop, $options: "i" },
createdAt: {
$gte: new Date(splittedFilter[2]),
$lte: new Date(splittedFilter[3]),
},
};
const previousCodes = await BlockedCode.aggregate([
{
$match: prevQuery,
},
{
$group: {
_id: "$discountCode",
totalCount: { $sum: "$count" },
},
},
]);
const result = currentCodes.map((code) => {
const foundPrevCode = previousCodes.find((i) => i._id === code._id);
if (foundPrevCode?._id) {
const prevCount = foundPrevCode?.totalCount;
const currCount = code?.totalCount;
const difference = currCount - prevCount;
const percentage = (difference / currCount) * 100;
return { ...code, percentage };
} else {
return { ...code, percentage: 100 };
}
});
#shahamar Rahman i don't understand percentage logic can you explain little more!
upto this i filtered and counted the data based October month,plz check it and let me know if it's helps you
https://mongoplayground.net/p/Fet3UUI9LDC
db.collection.aggregate([
{
$addFields: {
month: {
$month: {
$toDate: "$createdAt"
}
}
}
},
{
$match: {
month: 10
}
},
{
$group: {
_id: {
code: "$code"
},
count: {
$sum: "$count"
}
}
}
])

Getting directly an array from a mongoose model

I'm trying to query directly to an array inside of an mongoose document, but for the moment i have couldn't.
The document example:
{
_id:62141a799b646c7926fcfa9c
firstname:"firstname"
lastname:"lastname"
username:"username"
phone:"0000-0000000"
email:"Example#mail.com"
email_verified_at:null
password:"$2a$10$LUATvtyPmlojHVdCkxP/QO9UUzQgOoGCW6xyx8YPUZkt5l7j6kHxK"
remember_token:null
deleted_at:null
created_at:2022-02-21T23:04:25.097+00:00
updated_at:2022-02-21T23:04:25.097+00:00
notes: [
"621954b8f073154099b92fca"
"62142c426ca950e33baa1302"
]
I have a query that works but i think that isn't the most optimal approach, altough i could be wrong.
I want to get something like this:
[
"621954b8f073154099b92fca"
"62142c426ca950e33baa1302"
]
or populated:
[
{
_id: new ObjectId("621954b8f073154099b92fca"),
user: new ObjectId("62141a799b646c7926fcfa9c"),
type: new ObjectId("62076ce385b4eea8c5aeb8ba"),
title: 'qwasdasd',
content: '',
created_at: 2022-02-25T22:14:16.515Z,
updated_at: 2022-02-25T22:14:16.515Z
},
{
_id: new ObjectId("62142c426ca950e33baa1302"),
user: new ObjectId("62141a799b646c7926fcfa9c"),
type: new ObjectId("62076ce385b4eea8c5aeb8ba"),
title: 'qwasdasd',
content: '',
created_at: 2022-02-25T22:14:16.515Z,
updated_at: 2022-02-25T22:14:16.515Z
}
]
And even if i need to find an specific field, search it, but with the query:
const notes = await collection.findOne({ _id: '62141a799b646c7926fcfa9c', notes: { _id: '621954b8f073154099b92fca' } }, { 'notes.$._id': true });
i have this rersult:
{
_id: new ObjectId("62141a799b646c7926fcfa9c"),
notes: [
"621954b8f073154099b92fca"
]
}
or populated:
{
_id: new ObjectId("62141a799b646c7926fcfa9c"),
notes: [
{
_id: new ObjectId("621954b8f073154099b92fca"),
user: new ObjectId("62141a799b646c7926fcfa9c"),
type: new ObjectId("62076ce385b4eea8c5aeb8ba"),
title: 'qwasdasd',
content: '',
created_at: 2022-02-25T22:14:16.515Z,
updated_at: 2022-02-25T22:14:16.515Z
}
]
}
And i know that i can reach it filtering the ObjectId from user with '-_id notes.$._id' instead of { 'notes.$._id': true } and then destructuring the main object with a const { notes } resulting in this code:
const { notes } = await collection.findOne({ _id: '62141a799b646c7926fcfa9c', notes: { _id: '621954b8f073154099b92fca' } }, '-_id notes.$._id' );
But, it's the best approach to do this? Could i do it in different way being able to take advantage of findOne options like, skip, etc...?
PD: If there's some error it's because it's not the original code, i have modified it because in original code there're many abstractions.
Thanks in advance.

Mongoose - projection with $elemMatch on nested fields

I'm relatively new to MongoDB/Mongoose and I've only performed simple queries. Now I'm having some trouble trying to filter my database in a slightly more complex way. I already did some research to tackle my previous issues, but now I can't move forward. Here's what happening:
This is my schema:
const userSchema = new mongoose.Schema({
email: String,
password: String,
movies: [
{
title: String,
movieId: Number,
view_count: Number,
rating: Number,
review: String,
},
],
lists: {
watched_movies: [
{
title: String,
director: String,
genres: [{ type: String }],
runtime: Number,
date: Date,
},
],
},
});
I want to make a GET request that matches simultaneously "lists.watched_movies": { _id: req.params.entryId } and also "movies.title": req.body.title for a given email, so that the outcome of the findOne query would be just those elements and not the whole document. What I'm trying to accomplish is something like that:
{
email: "some.email#gmail.com",
movies: [
{
title: "Mongoose Strikes Back",
movieId: 123,
view_count: 1,
rating: 3,
review: "Very confusing movie!"
}
],
lists: {
watched_movies: [
{
_id: 4321
title: "Mongoose Strikes Back",
director: "Mongo",
genres: ["Drama"],
runtime: 150,
date: "2021-11-22"
}
]
}
}
My first attempt to tackle it, however, wasn't successful. Here's what I tried:
router.route("/:entryId").get((req, res) => {
User.findOne(
{ email: "some.email#gmail.com" },
{
"lists.watched_movies": { $elemMatch: { _id: req.params.entryId } },
movies: { $elemMatch: { title: req.body.title } },
},
(err, entry) => {
if (!err) {
res.send(entry);
console.log(entry);
} else {
console.log(err);
}
}
);
});
It says that Cannot use $elemMatch projection on a nested field. I thought that maybe I can solve it by changing my schema, but I'd like to avoid it if possible.
For your scenario, you can use $filter to filter document(s) in nested array field.
db.collection.find({
email: "some.email#gmail.com"
},
{
"lists.watched_movies": {
"$filter": {
"input": "$lists.watched_movies",
"cond": {
"$eq": [
"$$this._id",
4321// req.params.entryId
]
}
}
},
movies: {
$elemMatch: {
title: "Mongoose Strikes Back"// req.body.title
}
}
})
Sample Mongo Playground

get first and last values for each condition

I have a collection like this:
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-08T00:00:00Z'),
item_type: "book",
book_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdf23'),
rating: 6
},
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-05T00:00:00Z'),
item_type: "movie",
movie_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdfa'),
rating: 5
},
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-010T00:00:00Z'),
item_type: "song",
song_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdf13'),
rating: 9
}
There can be only one rating per item per user per day.
I would like to check how the ratings evolve between a period of time for a selection of users and items. I need only the first and the last rating for each book/movie/song.
I have no idea on how I could do this the most efficient way.
As for now, I'm retrieving all the ratings for all the users, and then parsing them with PHP.
db.ratings.find({user_id:{$in:[...]}, $or:[book_id:{$in:[...]}, song_id:{$in:[...]}, movie_id:{$in:[...]}, ], date:{$gte:.., $lte..} });
This is obviously unefficient but I don't know how to handle this case.
You can do it with mongodb mapReduce. So at first you need to filter your data on date range, selection of users and selection of items(query part). Then group by item(map part) and for each item select first and last days with corresponding ratings(reduce part).
Try the following query:
var query = {
user_id: {$in:[...]}
date: { $gte: dateFrom, $lt:dateTo},
$or: [
{book_id: {$in:[...]}},
{song_id:{$in:[...]}},
{movie_id:{$in:[...]}}
]
}
var map = function () {
emit(this.item_type, {
first : {rating: this.rating, date: this.date},
last: {rating: this.rating, date: this.date}
})
}
var reduce = function (key, values) {
var res = values[0];
for (var i=1; i<values.length; i++ ) {
if (values[i].first.date < res.first.date)
res.first = values[i].first;
if (values[i].last.date > res.last.date)
res.last = values[i].last;
}
return res;
}
db.collection.mapReduce( map , reduce , { out: { inline : true }, query: query } )