querying mongodb documents based on expiry field - mongodb

I have a collection with a field of "expiryTime". Now, I want to alert users when their subscription is about to expire. I want to do it when they have 1 day/1 week/1 month left to their expiration, but I don't know how to retrieve these documents. Any help will be appreciated

A bit difficult to provide a solution without any sample data. Try this one:
db.collection.aggregate([
{
$match: {
$expr: {
$gt: [
"$expireTime",
{ $dateAdd: { startDate: "$$NOW", unit: "day", amount: 1 } }
]
}
}
}
])

Related

Dynamic MongoDB aggregation using $$NOW

Fellows;
Would anyone be so kind as to shed a light on this matter which has been stumping me for a few days now? It should be something simple, but I can't seem to find where the problem lies...
I need to retrieve one year worth of records from a collection based on today's date. Simply put, if today is 2023-02-15, I need records created after 2022-02-15.
This works, of course:
[
{
$match: { _created: { $gte: ISODate("2022-02-15") } }
}
]
The problem is that, when trying to create an aggregation that does not need to constantly be updated, $$NOW does not seem to work and I cannot understand why.
For example, this does not work:
[
{
$match:
{
_created:
{
$gte:
{
$subtract: [ "$$NOW", { $multiply: [1000, 60, 60, 24, 365] } ]
}
}
}
}
]
And this doesn't either:
[
{
$set:
{
startDate: { $subtract: [ "$$NOW", { $multiply: [1000, 60, 60, 24, 365] } ] }
}
},
{
$match:
{
_created: { $gte: ISODate("$startDate") }
}
}
]
I have tried moving things around, parsing the result of $$NOW to get just the date part, converting it to different types... The result is always the same: No errors, but no filtered records either.
And I have the feeling the answer might be something really silly I am just overlooking.
Can someone more experienced lend a helping hand?
P.S.: The aggregation continues after the required records are retrieved, I just did not care to list what happens because the problem relates only to the matching of records. Our Mongod version is 6.0.3, I am working on MongoDB Compass and the idea is to save this aggregation as a query which provides answers to an API call.
You need to use $expr and $gte aggregation operator (do not mistake with $gte Query Operator). In MongoDB 5.0 and newer, you can also use Date Expression Operators like $dateSubtract
{
$match:
{
$expr:
{
$gte:
[ "$_created", { $dateSubtract: { startDate: "$$NOW", unit: "year", amount: 1 } } ]
}
}
}

Is there a way to use find and aggregate together in MongoDB?

I m a MongoDB begginer and I have the following problem:
I have a document format(sorry for lack of definition) as follows in MongoDB:
And I want to query the top 10 albums of the worst genre of a decade I choose.
Firstly I did an aggregate that gave me in the last stage the worst genre of the decade I choose to use as comparison later (BDA1 being my database and album my collection I want to aggregate and find on):
BDA1.album.aggregation(
[
{
$addFields: {
release_date: {
$toDate: "$release_date"
}
}
},
{
$addFields: {
sales_amount: {
$convert: {
input: "$sales_amount",
to: "int"
}
}
}
},
{
$match: {
"release_date": {
$gte: new ISODate("2009-01-01"),
$lt: new ISODate("2021-01-01")
}
}
},
{
$unwind: {
path: "$band.band_genre",
}
},
{
$group: {
_id: "$band.band_genre",
total: {
$sum: "$sales_amount"
}
}
},
{
$sort: {
total: 1
}
},
{
$limit: 1
}
])
(Sorry for the lack of good formatting but I took the code from a pipeline I used to do the aggregation in MongoDB Compass.)
That resulted in:
But my question now is: how do I do to use that aggregate result in what I can only assume is a find command where band.band_genre equals to the genre I just calculated in the aggregation?
I have been searching SO for a while with no results and google as well.
Any suggestions?
(Anything that I have forgot to mention that u feel is important to understand the problem please say and I will edit it in)

each document contains 3 fields with dates, is it possible to return the documents according to the date closest to today contained in those fields?

I have a database with a structure like this:
{
"bidding":"0ABF3",
"dates":{
"expiration_date_registration":ISODate("2020-08-24T23:51:25.000Z"),
"expiration_date_tender":ISODate("2020-08-23T23:51:25.000Z"),
"expiration_date_complaints":ISODate("2020-08-22T23:51:25.000Z")
}
},
{
"bidding":"0ABF4",
"dates":{
"expiration_date_registration":ISODate("2020-08-19T23:51:25.000Z"),
"expiration_date_tender":ISODate("2020-07-25T23:51:25.000Z"), // this is the closest expiration date with respect to today ("this question was asked on July 24)
"expiration_date_complaints":ISODate("2020-08-13T23:51:25.000Z")
}
}
I have 3 fields each containing a date. expiration_date_registration,expiration_date_tender,expiration_date_complaints
I would like that when a request is made to my database, it is returned in order of expiration date according to the dates contained in these 3 fields.
In this case the output should show the second document first (in this example it is the field with the date closest to today (this question was asked on July 24), "expiration_date_tender":ISODate("2020-07-29T25:51:25.000Z")) and so on, an order using these 3 fields to determine the order in which my documents will be displayed.
it's possible?
It's possible, but not very efficient. Here's an aggregate pipe that calculates the distance from now for each of your dates, then gets and sorts by the min distance.
https://mongoplayground.net/p/HwittyBRjzZ
https://mongoplayground.net/p/EGp20ftjh-P
db.collection.aggregate([
{
$addFields: {
datesArr: [
"$dates.expiration_date_registration",
"$dates.expiration_date_tender",
"$dates.expiration_date_complaints",
]
}
},
{
$addFields: {
distances: {
$map: {
input: "$datesArr",
in: {
$abs: {
$subtract: [
"$$NOW",
"$$this"
]
}
}
}
}
}
},
{
$addFields: {
minDist: {
$min: "$distances"
}
}
},
{
$addFields: {
closestDate: {
$let: {
vars: {
closestDateIndex: {
$indexOfArray: [
"$distances",
"$minDist"
]
}
},
in: {
$arrayElemAt: [
"$datesArr",
"$$closestDateIndex"
]
}
}
}
}
},
{
$sort: {
minDist: 1
}
}
])
You can use $subtract to find difference between today date ($currentDate) and "expiration_date_tender" and then user $sort to get the desired document on top. You can fetch the top document with collection.findOne() function

Get document on subarray containing date between aggregation mongodb

Provided following collection:
[
{
events: [
{
triggers: [
{
date: "2019-12-12T23:00:00"
}
]
}
]
}
]
I want to be able to pull the documents that have any date in between a range of dates, let's say today and tomorrow.
Using following query:
db.collection.aggregate([
{
$match: {
"events.triggers.date": {
$gte: "2019-12-11T23:00:00.000Z",
$lt: "2019-12-12T23:59:00.000Z"
}
}
}
]);
However, when I do this, the query seems to be looking at any document that has any date greater than and any date lower than but not necessarily in the same "trigger" object.
Anyone got any idea how you can filter in a subarray like this (I do more in my query afterwards so a find will not work) and have the date search be subitem specific?
You are almost there, just some mistakes in your query. This should work:
db.collection.aggregate([
{
'$match': {
'$and': [
{"events.triggers.date": { '$gte': "2019-12-11T23:00:00.000Z" }},
{"events.triggers.date": { '$lt': "2019-12-11T23:00:00.000Z" }}
]
}
}
]);
So I found it eventually.
Those looking for the solution. Here it is:
elemMatch
db.collection.aggregate([
{
$match: {
"events.triggers": {
$elemMatch: {
"date": {
$gte: "2019-12-11T23:00:00.000Z",
$lt: "2019-12-12T23:59:00.000Z"
}
}
}
}
}
]);

MongoDB use $last with $cond

Is there any way to get the last non-zero value in an aggregation. Is that possible?
Scenario:
I have an events collection, in which I store all the events from users. I want to fetch the list of users with last purchased items cost is $1.99 and logged in at least once last week.
My events collection will have records like
{_id:ObjectId("58af54d5ab7df73d71822708"),uid:1,event:"login"}
{_id:ObjectId("58db7189296fdedde1c04bc1"),uid:2,event:"login"}
{_id:ObjectId("5888419bfa4b69dc4af7c76c"),uid:2,event:"purchase",amount:3}
{_id:ObjectId("5888419bfa4b69dc4af7d45c"),uid:1,event:"purchase",amount:1.9}
{_id:ObjectId("5888819bfa4b69dc4af7c76c"),uid:1,event:"custom",type:3,value:2}
What am trying to do:
db.events.aggregate([{
{
$group: {
_id: uid,
last_login: {
$max: {
$cond: [{
$eq: ['$event', 'login']
}, '$_id', 0]
}
},
last_amount: {
$last: {
$cond: [{
$eq: ['$event', 'login']
}, '$_id', 0]
}
}
}
}
}, {
$match: {
last_purchase: {
$gte: ObjectId("58af54d50000000000000000")
},
last_amount: 1.9
}
}])
which obviously will fail because last will have 0 as the last item.
The output am expecting is
{_id:1,last_login:_id:ObjectId("58af54d5ab7df73d71822708"),last_amount:1.9}
The query is system generated. Please help.
To answer your question, you cannot modify the behaviour of $last using $cond. i.e. 'not the last entry because of a criteria'.
There are few alternatives that you could try:
Alter your document schema for the access pattern, especially if this is a frequently used query in your application. You should utilise the schema as a leverage to optimise your application queries.
Use one of the MongoDB supported drivers to process the expected outcome.
Depending on the use case, you could execute 2 separate aggregation queries; first query all the users uid that logged in at least once last week, and second to query only those users that have the last purchased item value of $1.99.
I found a workaround.Instead of $last, I used $push in the $group and added $slice with $setDifference in $project to remove null values. The query will now look something like
db.events.aggregate([{
{
$group: {
_id: uid,
last_login: {
$max: {
$cond: [{
$eq: ['$event', 'login']
}, '$_id', 0]
}
},
last_amount: {
$push: {
$cond: [{
$eq: ['$event', 'login']
}, '$_id', null]
}
}
}
}
},
{$project:{last_login:{$slice:[{$setDifference,'$last_login',[null]},-1,1]}}}
, {
$match: {
last_purchase: {
$gte: ObjectId("58af54d50000000000000000")
},
last_amount: 1.9
}
}])