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.
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.
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();
})}
})
Related
I have a json payload that looks like this
{
"data":{
"methods":[
[
{
"p_id":"01",
"description":"Test01",
"offline":true
}
],
[
{
"p_id":"02",
"description":"Test02",
"offline":false
}
],
[
{
"p_id":"03",
"description":"Test03",
"offline":true
}
]
]
}
}
How can I write a JSONPath expression to get the "p_id" where "offline"= false?
You can use a filter expression which selects all elements in an object or array that match the specified filter. For example, [?(#.offline === false)] will match any object if its offline property is strictly false.
So, if the object is always in the same place, you could do:
$.data.methods.*[?(#.offline === false)].p_id
Or, if you want to look for any object where offline is false and fetch p_id, you could use a recursive descent with the filter expression:
$..[?(#.offline === false)].p_id
Note: I used strict equality in my examples so it will only match with a boolean false. If you don't need/want that you could instead simply use a ! to negate the filter. E.g. [?(!#.offline)]
I have documents that contain an object array. Within that array are pulses in a dataset. For example:
samples: [{"time":1224960,"flow":0,"temp":null},{"time":1224970,"flow":0,"temp":null},
{"time":1224980,"flow":23,"temp":null},{"time":1224990,"flow":44,"temp":null},
{"time":1225000,"flow":66,"temp":null},{"time":1225010,"flow":0,"temp":null},
{"time":1225020,"flow":650,"temp":null},{"time":1225030,"flow":40,"temp":null},
{"time":1225040,"flow":60,"temp":null},{"time":1225050,"flow":0,"temp":null},
{"time":1225060,"flow":0,"temp":null},{"time":1225070,"flow":0,"temp":null},
{"time":1225080,"flow":0,"temp":null},{"time":1225090,"flow":0,"temp":null},
{"time":1225100,"flow":0,"temp":null},{"time":1225110,"flow":67,"temp":null},
{"time":1225120,"flow":23,"temp":null},{"time":1225130,"flow":0,"temp":null},
{"time":1225140,"flow":0,"temp":null},{"time":1225150,"flow":0,"temp":null}]
I would like to construct an aggregate pipeline to act on each collection of consecutive 'samples.flow' values above zero. As in, the sample pulses are delimited by one or more zero flow values. I can use an $unwind stage to flatten the data but I'm at a loss as to how to subsequently group each pulse. I have no objections to this being a multistep process. But I'd rather not have to loop through it in code on the client side. The data will comprise fields from a number of documents and could total in the hundreds of thousands of entries.
From the example above I'd like to be able to extract:
[{"time":1224980,"total_flow":123,"temp":null},
{"time":1225020,"total_flow":750,"temp":null},
{"time":1225110,"total_flow":90,"temp":null}]
or variations thereof.
If you are not looking for specific values to be on the time field, then you can use this pipeline with $bucketAuto.
[
{
"$bucketAuto": {
"groupBy": "$time",
"buckets": 3,
"output": {
total_flow: {
$sum: "$flow"
},
temp: {
$first: "$temp"
},
time: {
"$min": "$time"
}
}
}
},
{
"$project": {
"_id": 0
}
}
]
If you are looking for some specific values for time, then you will need to use $bucket and provide it a boundaries argument with precalculated lower bounds. I think this solution should do your job
I currently have a simple condition query for MongoDB that goes like this:
{
"$and": [
{
"ApplicationStatus": "UW_SECOND_LEVEL"
},
{
"Application.UnderWriterDecisions.ud_cc_total_aggregate_limit": {
"$lte": 300000, "$gt":100000
}
},
{
"Application.UnderWriterDecisions.UW_Recon_Count": {
"$eq": null
}
}
]
}
I need to add another condition to the query above that checks the size of an array (DecisionReason) that I have, and make sure that it only has a size of 1:
Application.UnderwriterDecisions.DecisionReason
You can use the $size operator. Also, you don't have to use the explicit $and operator, but just separate the clauses with commas. From this page in the documentation.
MongoDB provides an implicit AND operation when specifying a comma separated list of expressions. Using an explicit AND with the $and operator is necessary when the same field or operator has to be specified in multiple expressions.
Also, you do not need to use the $eq operator, so your final query would look something like this:
db.collection.find({
"ApplicationStatus": "UW_SECOND_LEVEL",
"Application.UnderWriterDecisions.ud_cc_total_aggregate_limit":
{ "$lte": 300000, "$gt": 100000 },
"Application.UnderWriterDecisions.UW_Recon_Count": null,
"Application.UnderWriterDecisions.DecisionReason":
{ "$size": 1 }
});
For more information about the $size operator, check out this page in the documentation.
I'd like to be able to query documents where an array attribute is either null or of zero length. I've tried using $or, like
{ things : { $or : [null, { $size : 0 } ] } }
but MongoDB complains about it:
{ "$err" : "invalid operator: $or", "code" : 10068 }
It appears that MongoDB doesn't want to use $and or $or on array attributes. I thought that simply querying for { things : { $size : 0 } } would return documents that had non-null arrays of zero length as well as null arrays, but that doesn't appear to be true.
How can I issue a query to find all documents where an array attribute is null or of zero length?
You can also do this with $in:
db.test.find({things: {$in: [null, []]}})
That will also include docs with things: [null].
Another way is to use $exists on the first element of the things array:
db.test.find({'things.0': {$exists: false}})
That will find the docs where things does not have at least one element. In contrast with the above, it will still include docs with things: null or things: [], but not things: [null].
When things is null, it is not of type Array. It is of type Null which is its own data-type. $size only matches arrays. Any document where the field is something different than an array will never be matched by it.
Your syntax error is in how you use the $or operator. You don't use it for a field, you use it as a field.
db.collection.find({
$or: [
{ things: { $size: 0 } }, // 0-elements array
{ things: null } // field which is null
]
});
I have an array of objects, and I want to query in a MongoDB collection for documents that have elements that match any objects in my array of objects.
For example:
var objects = ["52d58496e0dca1c710d9bfdd", "52d58da5e0dca1c710d9bfde", "52d91cd69188818e3964917b"];
db.scook.recipes.find({products: { $in: objects }}
However, I want to know if I can sort the results by the number of matches in MongoDB.
For example, at the top will be the "recipe" that has exactly three elements matches: ["52d58496e0dca1c710d9bfdd", "52d58da5e0dca1c710d9bfde", "52d91cd69188818e3964917b"].
The second selected has two recipes: i.e. ["52d58496e0dca1c710d9bfdd", "52d58da5e0dca1c710d9bfde"], and the third one only one: i.e. ["52d58496e0dca1c710d9bfdd"]
It would be great if you could get the number of items it had.
By using the aggregation framework, I think that you should be able to get what you need by the following MongoDB query. However, if you're using Mongoose, you'll have to convert this to a Mongoose query. I'm not certain this will work exactly as is, so you may need to play with it a little to make it right. Also, this answer hinges on whether or not you can use the $or operator inside of the $project operator and that it will return true. If that doesn't work, I think you'll need to use map-reduce to get what you need or do it server side.
db.recipes.aggregate(
// look for matches
{ $match : { products : { $or : objects }}},
// break apart documents to by the products subdocuments
{ $unwind : "$products" },
// search for matches in the sub documents and add productMatch if a match is found
{ $project : {
desiredField1 : 1,
desiredField2 : 1,
products : 1,
// this may not be a valid comparison, but should hopefully
// be true or 1 if there is a match
productMatch : { "$products" : { $or : objects }}
}},
// group the unwound documents back together by _id
{ $group : {
_id : "$_id",
products : { $push : "$products" },
// count the matched objects
numMatches : { $sum : "$productMatch" },
// increment by 1 for each product
numProducts : { $sum : 1 }
}},
// sort by descending order by numMatches
{ $sort : { numMatches : -1 }}
)