Get rows by comparing field dates in mongo db - mongodb

I am trying to compare two fields of collection mongo itself to get rows, but not able to get it.
[{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-29T05:01:15.370+0000"),
},
{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-19T05:01:15.370+0000"),
},
{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
}]
Query
db.getCollection("collection_name").aggregate([
{
$match: {
status: '1',
$or: [
{
date_modified: { $gt: ISODate('$date_created') }
},
{
date_modified: {
"$exists": false,
},
},
],
},
},
]);
Expected result:
[{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
"date_modified" : ISODate("2022-06-29T05:01:15.370+0000"),
},{
"date_created" : ISODate("2022-06-24T05:01:15.370+0000"),
}]
Current result: date_created is not defined

You need to use $expr for comparison between fields. Use $ifNull to cater your requirement for checking the existence for the date_created field.
db.collection.find({
$expr: {
$or: [
{
$eq: [
{
$ifNull: [
"$date_modified",
null
]
},
null
]
},
{
$gt: [
"$date_modified",
"$date_created"
]
}
]
}
})
Here is the Mongo Playground for your reference.

Related

How to fetch records based on the max date in mongo document array

Below is the document which has an array name datum and I want to filter the records based on group by year and filter by the types and max date.
{
"_id" : ObjectId("5fce46ca6ac9808276dfeb8c"),
"year" : 2018,
"datum" : [
{
"Type" : "1",
"Amount" : NumberDecimal("100"),
"Date" : ISODate("2018-05-30T00:46:12.784Z")
},
{
"Type" : "1",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2023-05-30T00:46:12.784Z")
},
{
"Type" : "2",
"Amount" : NumberDecimal("340"),
"Date" : ISODate("2025-05-30T00:46:12.784Z")
},
{
"Type" : "3",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2021-05-30T00:46:12.784Z")
}
]
}
The aggregate Query I tried.
[{$group: {
_id :"$year",
RecentValue :
{
$sum: {
$reduce: {
input: '$datum',
initialValue: {},
'in': {
$cond:
[
{
$and:
[
{$or:[
{ $eq: [ "$$this.Type", '2' ] },
{$eq: [ "$$this.Type", '3' ] }
]},
{ $gt: [ "$$this.Date", "$$value.Date" ] },
]
}
,
"$$this.Amount",
0
]
}
}
}
}
}}]
the expected output would be which having the max date "2025-05-30T00:46:12.784Z"
{
_id :2018,
RecentValue : 340
}
Please let me know what mistake I did in the aggregate query.
You can get max date before $group stage,
$addFields to get document that having max date from, replaced $or with $in condition and corrected return value
$group by year and sum Amount
db.collection.aggregate([
{
$addFields: {
datum: {
$reduce: {
input: "$datum",
initialValue: {},
"in": {
$cond: [
{
$and: [
{ $in: ["$$this.Type", ["2", "3"]] },
{ $gt: ["$$this.Date", "$$value.Date"] }
]
},
"$$this",
"$$value"
]
}
}
}
}
},
{
$group: {
_id: "$year",
RecentValue: { $sum: "$datum.Amount" }
}
}
])
Playground

How can I ensure my aggregation filters out subdocuments that are past expiry date in mongo?

I want to count the number of resetPassword (subdocument in Users schema) codes that are currently active. For a code to be active it's expiry date must be greater than the current date.
Here is my users schema. If someone requests to reset there password, we'll push a new { code: X, expiresAt, createdAt } Object to the array.
id: { type: String, unique: true },
resetPassword: [
{
code: String,
expiresAt: Date,
createdAt: Date,
},
],
I'm having an issue trying to $sum the total number of active reset codes. Here is the query I'm running that returns an empty array...note that if I were to remove the resetPassword.expiresAt: { $gt: nowDateInMilliseconds() } match section, it will return all the codes. I've tried moving this match statement out of the intial $match stage then doing an unwind & a match on the expiresAt but this didn't work either.
[
{
$match: {
"id": userId,
'resetPassword.expiresAt': {
$gt: nowDateInMillisec(),
},
},
},
{
$group: {
_id: '$id',
totalValidResetCodes: {
$sum: {
$size: '$resetPassword',
},
},
},
},
]
This returns an empty array, even though I've got the expiry dates set to a date in the future.
I also tried the following with the same result (notice how I added $unwind and another $match to the pipeline)
[
{
$match: {
"id": userId,
},
},
{
$unwind: '$resetPassword',
},
{
$match: {
'resetPassword.expiresAt': {
$gt: nowDateInMillisec(),
},
}
},
{
$group: {
_id: '$id',
totalValidResetCodes: {
$sum: {
$size: '$resetPassword',
},
},
},
},
]
nowDateInMillisec() - This simply returns the current date in milliseconds from epoch.
What am I doing wrong?
You can try $reduce in $project, instead of your all process, you need to return ISOdate from this nowDateInMillisec(),
db.collection.aggregate([
{ $match: { id: 1 } },
{
$project: {
totalValidResetCodes: {
$reduce: {
input: "$resetPassword",
initialValue: 0,
in: {
$add: [
"$$value",
{
$cond: [
{ $gt: ["$$this.expiresAt", nowDateInMillisec()] },
// If you really want to pass timestamp then try below line
// { $gt: ["$$this.expiresAt", { $toDate: nowDateInMillisec() }] },
1,
0
]
}
]
}
}
}
}
}
])
Playground
Below is my research, date format when stored in mongodb format should work for milliseconds as well. The below test is for up to a minute.
> db.users13.find().pretty();
{
"_id" : ObjectId("5f4e03768379a4e3f957641d"),
"id" : "johnc",
"resetPassword" : [
{
"code" : "abc",
"expiresAt" : ISODate("2020-09-02T09:11:18.394Z"),
"createdAt" : ISODate("2020-09-01T08:11:18.394Z")
},
{
"code" : "mno",
"expiresAt" : ISODate("2020-08-25T09:26:18.394Z"),
"createdAt" : ISODate("2020-08-25T08:11:18.394Z")
}
]
}
{
"_id" : ObjectId("5f4e06938379a4e3f957641f"),
"id" : "katey",
"resetPassword" : [
{
"code" : "j2c",
"expiresAt" : ISODate("2020-09-02T08:48:18.394Z"),
"createdAt" : ISODate("2020-09-01T08:11:18.394Z")
},
{
"code" : "rml",
"expiresAt" : ISODate("2020-09-01T08:26:18.394Z"),
"createdAt" : ISODate("2020-09-01T08:11:18.394Z")
}
]
}
> db.users13.aggregate([
{$unwind:"$resetPassword"},
{$match:{"resetPassword.expiresAt":{$gt:ISODate()}}}
]).pretty();
{
"_id" : ObjectId("5f4e03768379a4e3f957641d"),
"id" : "johnc",
"resetPassword" : {
"code" : "abc",
"expiresAt" : ISODate("2020-09-02T09:11:18.394Z"),
"createdAt" : ISODate("2020-09-01T08:11:18.394Z")
}
}
{
"_id" : ObjectId("5f4e06938379a4e3f957641f"),
"id" : "katey",
"resetPassword" : {
"code" : "j2c",
"expiresAt" : ISODate("2020-09-02T08:48:18.394Z"),
"createdAt" : ISODate("2020-09-01T08:11:18.394Z")
}
}
> ISODate()
ISODate("2020-09-01T08:31:37.059Z")
>
#Joe answered my question in the comments. He hinted that using a milliseconds since epoch time in the match filter wouldn't work since I'm using a Date type in my Mongoose schema.
So instead of doing this: $gt: nowDateInMillisec(), I simply used a Date type like so: $gt: new Date(),

How to write a complex query using `expr` in mongodb?

I'm trying to write a bit complicated query to the MongoDB and I honestly don't know how to write it correctly. In SQL pseudocode it's something like:
SELECT *
FROM Something
WHERE
(IF Item.NestedItem THEN NULL ELSE Item.NestedItem.Id) == 'f4421f9e-5962-4c68-8049-a45600f36f4b'
OR (IF Item.NestedItem THEN NULL ELSE Item.NestedItem.OtherId) == 'd6b799dc-f464-4919-8435-a7b600cc408a'
I succeeded in writing IF as
{ "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.Id"] }
{ "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.OtherId"] }
But now I'm not sure how to compare them to constants and then join. I'm trying to write a translator from SQL-like language to MongoDb and this is example of query I cannot translate. I want something like
{ "$and" :
{ "$eq" : { "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.Id"] }, "f4421f9e-5962-4c68-8049-a45600f36f4b" },
{ "$eq" : { "$cond" : [{ "$eq" : ["$Item.NestedItem", null] }, null, "$Item.NestedItem.OtherId"] }, "d6b799dc-f464-4919-8435-a7b600cc408a" } }
But it's an invalid query as I get it
The $cond is a aggregation expression, you need to use $expr before $and operation, in your SQL query you have user OR so just updating in query $and to $or
corrected $eq syntax after $or
db.collection.aggregate([
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$cond: [
{ $eq: ["$Item.NestedItem", null] },
null,
"$Item.NestedItem.Id"
]
},
"f4421f9e-5962-4c68-8049-a45600f36f4b"
]
},
{
$eq: [
{
$cond: [
{ $eq: ["$Item.NestedItem", null] },
null,
"$Item.NestedItem.OtherId"
]
},
"d6b799dc-f464-4919-8435-a7b600cc408a"
]
}
]
}
}
}
])
Playground

On MongoDb Aggregation lookup, does the let need special formating?

I am trying to use the MongoDB $lookup with the Uncorrelated Subqueries.
Using MongoDB 3.6.12 (support began on 3.6)
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
The following pipeline step is working, however if I swap out the first "userB" with the second, no results are returned.
{
from: 'friendships',
let: { requestuser: ObjectId("5c0a9c37b2365a002367df79"), postuser: ObjectId("5c0820ea17a69b00231627be") },
pipeline: [
{
$match : {
$or : [
{
"userA" : ObjectId("5c0820ea17a69b00231627be"),
"userB" : ObjectId("5c0a9c37b2365a002367df79")
// "userB" : '$$requestuser'
},
{
"userA" : ObjectId("5c0a9c37b2365a002367df79"),
"userB" : ObjectId("5c0820ea17a69b00231627be")
}
],
"accepted" : true
}
},
{
$project: {v: 'true'}
}
],
as: "match"}
Results with hard coded ObjectId:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : "true"
}
]
Results using variable:
"match" : [
]
I feel like ObjectIds need special treatment. All the examples I could find are using simple variables like strings.
To verify the '$$requestUser' contained a value, I tested it on the projection:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : ObjectId("5c0a9c37b2365a002367df79")
}
]
When you use un co-related sub queires, you need to use $expr to pass a variable.
You can try something like following.
{
$match: {
$expr: {
$or: [
{
$and:[
{
$eq: [ "userA", ObjectId("5c0820ea17a69b00231627be") ]
},
{
$eq: [ "userB", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", "$$requestuser" ]
}
]
},
{
$and:[
{
$eq: [ "userA", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", ObjectId("5c0820ea17a69b00231627be") ]
}
]
}
]
},
"accepted": true,
}
}
I have created a sample demo to show how $expr works inside the lookup : Sample demo for Uncorrelated Subquery

mongodb aggregate to find,count and project unique documnets

Below are the sample collection.
col1:
"_id" : ObjectId("5ec293782bc00b43b463b67c")
"status" : ["running"],
"name" : "name1 ",
"dcode" : "dc001",
"address" : "address1",
"city" : "city1"
col2:
"_id" : ObjectId("5ec296182bc00b43b463b68f"),
"scode" : ObjectId("5ec2933df6079743c0a2a1f8"),
"ycode" : ObjectId("5ec293782bc00b43b463b67c"),
"city" : "city1",
"lockedDate" : ISODate("2020-05-20T00:00:00Z"),
"_id" : ObjectId("5ec296182bc00b43b463688b"),
"scode" : ObjectId("5ec2933df6079743c0a2a1ff"),
"ycode" : ObjectId("5ec293782bc00b43b463b67c"),
"city" : "city1",
"lockedDate" : ISODate("2020-05-20T00:00:00Z"),
"_id" : ObjectId("5ec296182bc00b43b44fc6cb"),
"scode" :null,
"ycode" : ObjectId("5ec293782bc00b43b463b67c"),
"city" : "city1",
"lockedDate" : ISODate("2020-05-20T00:00:00Z"),
problemStatement:
I want to display name from col1 & count of documents from col2 according to ycode where scode is != null
Tried attempt:
db.col1.aggregate([
{'$match':{
city:'city1'
}
},
{
$lookup:
{
from: "col2",
let: {
ycode: "$_id",city:'$city'
},
pipeline: [
{
$match: {
scode:{'$ne':null},
lockedDate:ISODate("2020-05-20T00:00:00Z"),
$expr: {
$and: [
{
$eq: [
"$ycode",
"$$ycode"
]
},
{
$eq: [
"$city",
"$$city"
]
}
]
},
},
},
], as: "col2"
}
},
{'$unwind':'$col2'},
{'$count':'ycode'},
{
$project: {
name: 1,
status: 1,
}
},
])
now problem with this query is it either displays the count or project the name & status i.e if i run this query in the current format it gives {} if I remove {'$count':'ycode'} then it project the values but doesn't give the count and if I remove $project then i do get the count {ycode:2} but then project doesn't work but I want to achieve both in the result. Any suggestions
ORM: mongoose v>5, mongodb v 4.0
You can try below query :
db.col1.aggregate([
{ "$match": { city: "city1" } },
{
$lookup: {
from: "col2",
let: { id: "$_id", city: "$city" }, /** Create local variables from fields of `col1` but not from `col2` */
pipeline: [
{
$match: { scode: { "$ne": null }, lockedDate: ISODate("2020-05-20T00:00:00Z"),
$expr: { $and: [ { $eq: [ "$ycode", "$$id" ] }, { $eq: [ "$city", "$$city" ] } ] }
}
},
{ $project: { _id: 1 } } // Optional, But as we just need count but not the entire doc, holding just `_id` helps in reduce size of doc
],
as: "col2" // will be an array either empty (If no match found) or array of objects
}
},
{
$project: { _id: 0, name: 1, countOfCol2: { $size: "$col2" } }
}
])
Test : mongoplayground