Reputation: 417
I have a model which contains an array with dates. I'm using a $gte
operator as a condition to query the collection where all the elements in the array of dates are $gte
a given date.
For example I have this document:
{ dates: [
ISODate("2016-10-24T22:00:00.000+0000"),
ISODate("2017-01-16T23:00:00.000+0000")]
}
When I run this query {dates: {$gte: new Date()}}
, it gives me the whole document as a result. But I want a result where every single array item matches my query, not just one.
Upvotes: 3
Views: 2333
Reputation: 103435
You can also use the aggregation framework with the $redact
pipeline operator that allows you to proccess the logical condition with the $cond
operator and uses the special operations $$KEEP
to "keep" the document where the logical condition is true or $$PRUNE
to "remove" the document where the condition was false.
This operation is similar to having a $project
pipeline that selects the fields in the collection and creates a new field that holds the result from the logical condition query and then a subsequent $match
, except that $redact
uses a single pipeline stage which is more efficient.
As for the logical condition, there are Set Operators that you can use since they allow expression that perform set operations on arrays, treating arrays as sets. These couple of these operators namely the $allElementTrue
and $map
operators can be used as the logical condition expression as they work in such a way that if all of the elements in the array actually are $gte
a specified date, then this is a true match and the document is "kept". Otherwise it is "pruned" and discarded.
Consider the following examples which demonstrate the above concept:
Populate Test Collection
db.test.insert([
{ dates: [
ISODate("2016-10-24T22:00:00.000+0000"),
ISODate("2017-01-16T23:00:00.000+0000")]
} ,
{ dates: [
ISODate("2017-01-03T22:00:00.000+0000"),
ISODate("2017-01-16T23:00:00.000+0000")]
}
])
$redact
with $setEquals
db.test.aggregate([
{ "$match": { "dates": { "$gte": new Date() } } },
{
"$redact": {
"$cond": [
{
"$allElementsTrue": {
"$map": {
"input": "$dates",
"as": "date",
"in": { "$gte": [ "$$date", new Date() ] }
}
}
},
"$$KEEP",
"$$PRUNE"
]
}
}
])
Sample Output
{
"_id" : ObjectId("581899dda450d81cb7d87d3a"),
"dates" : [
ISODate("2017-01-03T22:00:00.000Z"),
ISODate("2017-01-16T23:00:00.000Z")
]
}
Another not-so elegant approach would be to use $where
(as a last resort) with the Array.prototype.every()
method:
db.test.find({ "$where": function(){
return this.dates.every(function(date) {
return date >= new Date();
})}
})
Upvotes: 3
Reputation: 312045
You can do this by using $not
and the inversion of your comparison condition:
db.test.find({dates: {$not: {$lt: new Date()}}})
So this matches docs where it's not the case that there's a dates
element with a value less than the current time; in other words, all dates
values are >= the current time.
Upvotes: 5