$or query in mongodb - mongodb

I need find field create_date, but if create_date is not defined, then find field create_time:
Sources_Timings.find({
create_date: {
$or: [
{
$gte: req.body.date.starting,
$lte: req.body.date.ending
},
{
$exists: false
}
]
},
create_time: {
$or: [
{
$gte: 0,
$lt: 86400000
},
{
$exists: false
}
]
}
},
function(err, timings) {
...
})
My code don't working.

$or is a top level operator and perform operation on an array of two or more expressions and selects the documents that satisfy at least one of the expressions.
So you query should be some thing like this
Sources_Timings.find({
"$or": [
{
"create_date": {
"$gte": req.body.date.starting,
"$lte": req.body.date.ending
}
},
{
"create_time": {
"$gte": 0,
"$lt": 86400000
}
}
]
})

Related

MongoDB Query on multiple field condition

I have a MongoDB model that is currently like this (this is the stripped version):
{
title: String,
type: {
type: String,
lowercase: true,
enum: ['event', 'regular', 'project'],
},
project_start_time: Date,
project_end_time: Date,
regular_start_date: Date,
regular_end_date: Date,
events: [{
id: Number,
date: Date
}]
}
Now, I want to query something like this:
Find data where the regular_end_date, project_end_time, and events at the last index are lower than the date provided
The catch is, not every data has the three criteria above because it is available according to the types (Sorry for the messy data, it is already there). Below is an example:
If the data type is an event, then there are events
If the data type is regular, then there are regular_start_date and regular_end_date
If the data type is a project, then there are project_start_date and project_end_date
So far, I've tried to use this:
db.data.find({
"$or": [
{
"project_end_time": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
},
},
{
"regular_end_date": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
}
},
{
"$expr": {
"$lt": [
{
"$getField": {
"field": "date",
"input": {
"$last": "$events"
}
}
},
ISODate("2022-12-27T10:09:49.753Z")
]
}
}
]
})
Also with aggregation pipeline:
db.data.aggregate([
{
$match: {
"$or": [{
"project_end_time": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
},
},
{
"regular_end_date": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
}
},
{
"$expr": {
"$lt": [{
"$getField": {
"field": "date",
"input": {
"$last": "$events"
}
}
},
ISODate("2022-12-27T10:09:49.753Z")
]}
}]
}
}
])
But it shows all data as if it wasn't filtered according to the criteria. Any idea where did I do wrong?
FYI I am using MongoDB 5.0.2
One option is to check if the relevant field exists before checking its value, otherwise its value is null which is less than your requested date:
db.collection.find({
$or: [
{$and: [
{project_end_time: {$exists: true}},
{project_end_time: {$lt: ISODate("2022-12-27T10:09:49.753Z")}}
]},
{$and: [
{regular_end_date: {$exists: true}},
{regular_end_date: {$lt: ISODate("2022-12-27T10:09:49.753Z")}}
]},
{$and: [
{"events.0": {$exists: true}},
{$expr: {
$lt: [
{$last: "$events.date"},
ISODate("2022-12-27T10:09:49.753Z")
]
}}
]}
]
})
See how it works on the playground example

MongoDB Aggregation Pipeline - get oldest document, but debounced

I have documents that look like:
[
{
value: 'Apple',
createdAt: '2021-12-09T20:15:26.421+00:00',
},
{
value: 'Blueberry',
createdAt: '2021-12-09T20:45:26.421+00:00',
},
{
value: 'Cranberry',
createdAt: '2021-12-09T21:30:26.421+00:00',
},
{
value: 'Durian',
createdAt: '2022-01-24T20:15:26.421+00:00',
},
{
value: 'Elderberry',
createdAt: '2022-01-24T20:45:26.421+00:00',
},
]
I'd like to do an aggregation where I get the oldest document, with the caveat that if another document was created within an hour, it invalidates the first document and I actually want to grab that one instead. For example, in the above I would like to return Cranberry. Initially pick Apple, but since Blueberry comes within an hour, move to that one, and since Cranberry comes within an hour of Blueberry, select Cranberry.
You can do the followings in an aggregation pipeline:
$sort by createdAt
$limit to get the oldest document
$lookup to get all the documents with createdAt behind the current document
$reduce to loop the result array; update the accumulator/result only if the current entry is within 1 hour
db.collection.aggregate([
{
$sort: {
createdAt: 1
}
},
{
$limit: 1
},
{
"$lookup": {
"from": "collection",
let: {
current: "$createdAt"
},
pipeline: [
{
$match: {
$expr: {
$gte: [
"$createdAt",
"$$current"
]
}
}
}
],
"as": "within"
}
},
{
"$addFields": {
"within": {
"$reduce": {
"input": "$within",
"initialValue": null,
"in": {
"$cond": {
"if": {
$or: [
{
$eq: [
"$$value",
null
]
},
{
$lte: [
{
"$subtract": [
"$$this.createdAt",
"$$value.createdAt"
]
},
3600000
]
}
]
},
"then": "$$this",
"else": "$$value"
}
}
}
}
}
}
])
Here is the Mongo playground for your reference.

Query using $dateFromString and $lt and $lge in mongodb

I am trying to return some records for date range from mongodb and I am trying to use the following query to query the collection test for the field StartDate:
db.test.aggregate([
{
$match: {
"StartDate": {
"$gte": [
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
],
"$lt": [
{
"$dateFromString": {
"dateString": "2021-04-01T00:00:00.0000000Z"
}
}
]
}
}
}
])
The above query didn't return anything but I am sure there are some data between the dates. Any ideas what have I missed? Thanks!
The $dateFromString is a aggregation pipeline operator, so it requires to match in $expr expression condition, and expression have different syntax of $gte and $lt,
db.test.aggregate([
{
$match: {
$expr: {
$and: [
{
$gte: [
"$StartDate",
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
]
},
{
$lt: [
"$StartDate",
{
"$dateFromString": {
"dateString": "2021-04-01T00:00:00.0000000Z"
}
}
]
}
]
}
}
}
])
Playground
Second option: You can use $toDate operator alternate of $dateFromString,
Playground
Update as per new requirement:
Field activities.dateOfActivity is an array, it requires to iterate through loop and check each date's conditions,
$map to iterate loop of activities.dateOfActivity array and put both condition inside, it will return true if both condition satisfy otherwise return false
$anyElementTrue will check return array of boolean have any true condition then return document
db.test.aggregate([
{
$match: {
$and: [
{
"$expr": {
$anyElementTrue: {
$map: {
input: "$activities.dateOfActivity",
in: {
$and: [
{
"$gte": [
"$$this",
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
]
},
{
"$lt": [
"$$this",
{
"$dateFromString": {
"dateString": "2021-04-20T00:00:00.0000000Z"
}
}
]
}
]
}
}
}
}
}
]
}
}
])
Playground

MongoDB, is there a statement similar to if then elif in sql?

Query is to find people whose age is shown and if their grade is 5 or higher then they will get honours, othewise fail. Can I use $cond with find?
db.test.find({age:{$exists:true}},{$cond: {if: "grade":5, then:"Honours" : true, else: "Honours" : "fail"}})
I also tried
db.test.find({$and: [{age:{$exists:true}}, {grade: {$gte:"5"}}]}, {$set:{"Honours":"true"}}, {multi:true})
But neither come close. Any help/direction is very much appreciated.
Check this.
db.test.aggregate([
{
$match: {
"age": {
"$exists": true
}
}
},
{
$project: {
"Honours": {
$switch: {
branches: [
{
case: {
"$gte": [
"$grade",
5
]
},
then: "true"
}
],
default: "fail"
}
}
}
}
])
.find projection only allows include / exclude fields, neither transform them nor add new field. Use MongoDB aggregation
db.test.aggregate([
{
$match: {
age: {
$exists: true
}
}
},
{
$project: {
Honours: {
$cond: [
{
$gte: [
"$grade",
5
]
},
true,
"fail"
]
}
}
}
])
MongoPlayground

Returning a document with two fields from the same array in MongoDB

Given documents such as
{
_id: 'abcd',
userId: '12345',
activities: [
{ status: 'login', timestamp: '10000001' },
{ status: 'logout', timestamp: '10000002' },
{ status: 'login', timestamp: '10000003' },
{ status: 'logout', timestamp: '10000004' },
]
}
I am trying to create a pipeline such as all users that have their latest login/logout activities recorded between two timestamps will be returned. For example, if the two timestamp values are between 10000002 and 10000003, the expected document should be
{
_id: 'abcd',
userId: '12345',
login: '10000003',
logout: '10000002'
}
Of if the two timestamp values are between -1 and 10000001, the expected document should be :
{
_id: 'abcd',
userId: '12345',
login: '10000001',
logout: null
}
Etc.
I know it has to do with aggregations, and I need to $unwind, etc., but I'm not sure about the rest, namely evaluating two fields from the same document array
You can try below aggregation:
db.col.aggregate([
{
$unwind: "$activities"
},
{
$match: {
$and: [
{ "activities.timestamp": { $gte: "10000001" } },
{ "activities.timestamp": { $lte: "10000002" } }
]
}
},
{
$sort: {
"activities.timestamp": -1
}
},
{
$group: {
_id: "$_id",
userId: { $first: "$userId" },
activities: { $push: "$activities" }
}
},
{
$addFields: {
login: { $arrayElemAt: [ { $filter: { input: "$activities", as: "a", cond: { $eq: [ "$$a.status", "login" ] } } } , 0 ] },
logout: { $arrayElemAt: [ { $filter: { input: "$activities", as: "a", cond: { $eq: [ "$$a.status", "logout" ] } } } , 0 ] }
}
},
{
$project: {
_id: 1,
userId: 1,
login: { $ifNull: [ "$login.timestamp", null ] },
logout: { $ifNull: [ "$logout.timestamp", null ] }
}
}
])
We need to use $unwind + $sort + $group to make sure that our activities will be sorted by timestamp. After $unwind you can use $match to apply filtering condition. Then you can use $filter with $arrayElemAt to get first (latest) value of filtered array. In the last $project you can explicitly use $ifNull (otherwise JSON key will be skipped if there's no value)
You can use below aggregation
Instead of $unwind use $lte and $gte with the $fitler aggregation.
db.collection.aggregate([
{ "$project": {
"userId": 1,
"login": {
"$max": {
"$filter": {
"input": "$activities",
"cond": {
"$and": [
{ "$gte": ["$$this.timestamp", "10000001"] },
{ "$lte": ["$$this.timestamp", "10000004"] },
{ "$lte": ["$$this.status", "login"] }
]
}
}
}
},
"logout": {
"$max": {
"$filter": {
"input": "$activities",
"cond": {
"$and": [
{ "$gte": ["$$this.timestamp", "10000001"] },
{ "$lte": ["$$this.timestamp", "10000004"] },
{ "$lte": ["$$this.status", "logout"] }
]
}
}
}
}
}}
])