Spring Data MongoDB query with multiple optional parameters $in Collection - mongodb

I have a use case where my query need to find based on 3 optional fields and one of them is pass in as a collection.
The "3 optional fields" part is already solved by this post
Spring Data MongoDB AND/OR query with multiple optional parameters
However, I am runnig into the issue with the $in for the collection filed.
For example, in the following query
{ $and :
[{
$and:
[
{$or : [ { $where: '?0 == null' } , { a : ?0 }]},
{$or : [ { $where: '?1 == null' } , { b : ?1 }]},
{$or : [ { $where: '?2 == null' } , { c : ?2 }]}
]
}]
}
In my case, the field a is actually pass in as a collection, I need to find those object in MongoDB with filed a is $in the collection I passed in.
I have tried with something like this
{ $and :
[{
$and:
[
{$or : [ { $where: '?0 == null' } , { a : { $in : ?0 }}]},
{$or : [ { $where: '?1 == null' } , { b : ?1 }]},
{$or : [ { $where: '?2 == null' } , { c : ?2 }]}
]
}]
}
However, I got NPE for this when I pass in a null collection.

You need to test the collection length, as shown here spring-data-mongo - optional query parameters?
You can also get rid of the top-level $and.
{ $and :
[
{$or : [ { $where: '?0.length == 0' } , { a : { $in : ?0 }}]},
{$or : [ { $where: '?1 == null' } , { b : ?1 }]},
{$or : [ { $where: '?2 == null' } , { c : ?2 }]}
]
}

You actually can't use $in for optional list parameter because when not specified it will be null and $in accept only list. To do that You will have add [] around optional list parameter and then flatten this list like that:
{ $and :
[
{$or : [ { $where: '?0 == null;' },
{ $where: '[].concat.apply([],[?0]).indexOf(this.a) !== -1;'}]},
{$or : [ { $where: '?1 == null' } , { b : ?1 }]},
{$or : [ { $where: '?2 == null' } , { c : ?2 }]}
]
}
use that for mongo version < 3.4 and since 3.4 You can use includes function instead of indexOf

Related

Having problem with date comparison in #Query [mongo/springboot]

I am trying to create a custom Query annotation in a MongoRepository. Everything is fine except the date comparison. I need to find all items that were created on a specific day, so I give 2 date objects with the good date and the times fixed to 00:00 and 23:59 to gte and lte operators.
This is the request sent according to the debug trace:
2022-08-27 01:52:25.817 DEBUG 440959 --- [or-http-epoll-4] o.s.data.mongodb.core.MongoTemplate : find using query: { "$and" : [{ "$or" : [{ "$where" : "5 == null"}, { "rating" : 5}]}, { "$or" : [{ "$where" : "null == null"}, { "productId" : null}]}, { "$or" : [{ "$where" : "APPROVEDD == null"}, { "moderationStatus" : "APPROVEDD"}]}, { "$or" : [{ "$where" : "true == false"}, { "clientResponses" : { "$exists" : true, "$not" : { "$size" : 0}}}]}, { "$or" : [{ "$where" : "2022-01-29T23:00:00Z == null || 2022-01-30T22:59:59Z == null"}, { "submissionTime" : { "$gte" : { "$date" : "2022-01-29T23:00:00Z"}, "$lte" : { "$date" : "2022-01-30T22:59:59Z"}}}]}]} fields: Document{{}} for class: class com.company.productreviews.cdpresponseportalapi.model.Review in collection: reviews
Dates seem to be in the good format, but mongo gives me an error (only when I use the date filter of my request):
Query failed with error code 139 and error message 'SyntaxError: identifier starts immediately after numeric literal' on server mongodb-databaserevqa-shard-00-02.xxocp.mongodb.net:27017; nested exception is com.mongodb.MongoQueryException: Query failed with error code 139 and error message 'SyntaxError: identifier starts immediately after numeric literal' on server mongodb-databaserevqa-shard-00-02.xxocp.mongodb.net:27017
My repo:
public interface ReviewRepository extends MongoRepository<Review, String> {
#Query("{ $and : [" +
"{ $or : [ { $where: '?0 == null' }, { 'rating': ?0 } ] }," +
"{ $or : [ { $where: '?1 == null' }, { 'productId': ?1 } ] }," +
"{ $or : [ { $where: '?2 == null' }, { 'moderationStatus': ?2 } ] }," +
"{ $or : [ { $where: '?3 == false' }, { 'clientResponses': { $exists: true, $not: { $size: 0 } } } ] }," +
"{ $or : [ { $where: '?4 == null || ?5 == null' }, { 'submissionTime': { $gte: ?4, $lte: ?5 } } ] }," +
"]}")
List<Review> findAll(Integer rating, Integer productId, String moderationStatus, boolean withAnswersOnly, Date submissionDateStartRange, Date submissionDateEndRange);
Review findOneByReviewId(int reviewId);
}
It seems to be a syntax error when your date value are not null. Wrapping your statement in quote should do the trick. So in your last #Query statement do the following:
"{ $or : [ { $where: '\"?4\" == \"null\" || \"?5\" == \"null\"' }, { 'submissionTime': { $gte: ?4, $lte: ?5 } } ] },"

How to implement a custom filter with mongodb schema?

I need to know is there any method that I can implement to have a custom filter for a complex search?
For example,
User.someMethodName((eachUser) => {
const temp = eachUser.temperature;
if(temp.mesure === 'C' && temp.value > 36) return true;
if(temp.mesure === 'F' && convertToCel(temp.value) > 36) return true;
return false;
})
something like this. I know that I can await User.find({}) and use the array filter method on it to filter but my concern is that in that case, I wouldn't be able to do my pagination which I can do with mongoose limit(), skip() methods.
Any help would be highly appreciated. Thanks!
Edit: Just found out can use aggregate with $redact to achieve this, but still not figured out how exactly implement it to meet this need.
You can use the $switch aggregate framework operator.
https://docs.mongodb.com/manual/reference/operator/aggregation/switch/#example
Here an example based on your requirements
Sample documents
One document per case
{
"name" : "sensor1",
"measure" : "C",
"value" : 36
}
{
"name" : "sensor2",
"measure" : "F",
"value" : 78
}
{
"name" : "sensor3",
"measure" : "C",
"value" : 12
}
{
"name" : "sensor4",
"measure" : "F",
"value" : 113
}
Query
Using different cases you can cover all scenario based on the measure and the value.
Fahrenheit formula is set inside the case operator.
Add your sort operator and other match conditions if you need.
$skip and $limit can be set dynamically using code
db.test.aggregate(
[{$project : {
"name" : 1,
"measure": 1,
"value": 1,
"result" :
{ $switch:
{
branches: [
{
//when your Celsius temp is greater and equals to 36
case: { $and : [ { $eq : [ "$measure" , "C" ] },
{ $gte : [ "$value" , 36 ] } ] },
then: true
},
{
//when your Celsius temp is less then 36
case: { $and : [ { $eq : [ "$measure" , "C" ] },
{ $lt : [ "$value" , 36 ] } ] },
then: false
},
{
//when your Fahrenheit temp is greater and equals to 36
case: { $and : [ { $eq : [ "$measure" , "F" ] },
{ $gte : [ {"$multiply" : [ { "$sum":
["$value", -32]}, 5/9]} , 36 ] } ] },
},
then: true
},
{
//when your Fahrenheit temp is less than 36
case: { $and : [ { $eq : [ "$measure" , "F" ] },
{ $lt : [ {"$multiply" : [ { "$sum":
["$value", -32]}, 5/9]} , 36 ] } ] }, ] },
then: false
}
],
default: "error"
}
}}},
{$skip : 0},
{$limit : 2}])
Result
{
"name" : "sensor1",
"measure" : "C",
"value" : 36,
"result" : true
}
{
"name" : "sensor2",
"measure" : "F",
"value" : 78,
"result" : false
}

mongodb - how to use $project with $or

I have a following collection:
_id,
one,
two,
messages: [{
sender
seen
}]
and I need to get count for 'messages' in each collection, where (one=1 AND sender IS NOT 1) OR (two=1 AND sender IS NOT 1)
'$or': [
{ 'one': 1, 'messages.sender': {$ne: 1},
{ 'two': 1, 'messages.sender': {$ne: 1}
]
edit
When I try to $project with $or, returns me nothing
Conversation.aggregate([
{
'$or': [
{ 'one': 1, 'messages.sender': {$ne: 1},
{ 'two': 1, 'messages.sender': {$ne: 1}
]
},
{
$project: {
count: { $size:"$messages" }
}
}
]).exec(function (err, c) {
res.json(c);
});
I'm really stuck with this and have no clue how to get the results.
Thanks for help
You can use $cond operator in 2.6 version as a work around solution.
Assuming that we have the next collection :
{
"_id" : ObjectId("5ad469bb18a5a3164402d279"),
"Show" : true,
"Name" : "Alamir"
}
{
"_id" : ObjectId("5ad469c318a5a3164402d27a"),
"Show" : false,
"Name" : "Alamir"
}
and we want to project the result based on 'Show' filed as (If show == true then show the field , else hide the filed from the result set )
So the query will be :
db.myCollection.aggregate([
{ $project :
{ Name : 1 ,
Show :
{
$cond : { if : { $eq : ["$Show" , true] } , then : "$Show" , else : "" }
}
}}])

How to retrieve objects inside an array, if matches given condition, using mongodb 3.0.0

I want to project all the objects from an array, if it matches the given condition.
I have following data
{
_id : 1,
em : 'abc#12s.net',
name : 'NewName',
od :
[
{
"oid" : ObjectId("1234"),
"ca" : ISODate("2016-05-05T13:20:10.718Z")
},
{
"oid" : ObjectId("2345"),
"ca" : ISODate("2016-05-11T13:20:10.718Z")
},
{
"oid" : ObjectId("57766"),
"ca" : ISODate("2016-05-13T13:20:10.718Z")
}
]
},
{
_id : 2,
em : 'ab6c#xyz.net',
name : 'NewName2',
od :
[
{
"oid" : ObjectId("1234"),
"ca" : ISODate("2016-05-11T13:20:10.718Z")
},
{
"oid" : ObjectId("2345"),
"ca" : ISODate("2016-05-12T13:20:10.718Z")
},
{
"oid" : ObjectId("57766"),
"ca" : ISODate("2016-05-05T13:20:10.718Z")
}
]
}
I want to get all the objects from od array, if 'od.ca' comes between range say, if greater than 10th may and less than 15th may.
I tried using aggregate method of mongodb and I am new to this method. My query is as given below.
db.userDetail.aggregate(
{
$match:
{
'od.ca':
{
'$gte': '10/05/2016',
'$lte': '15/05/2016'
},
lo: { '$ne': 'd' }
}
},
{
$redact:
{
$cond:
{
if:
{
$gte: [ "$$od.ca", '10/05/2016' ],
$lte : ["$$od.ca" , '15/05/2016']
},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
})
When I am trying to use this command, getting error :-
assert: command failed: {
"errmsg" : "exception: Use of undefined variable: od",
"code" : 17276,
"ok" : 0
} : aggregate failed
Since I am using mongodb 3.0.0 I can not use $fiter. So I tried using $redact.
Can someone tell me what wrong I am doing? Is the query correct?
Also referred question Since I am not using 3.2 of mongodb (as I have mentioned), can not use the accepted answer of the question.
query explanation:
$match - match documents for criteria - limit documents to process
$unwind - econstructs od array field from the input documents to output a document for each element. Each output document is the input document with the value of the array field replaced by the element.
$match - match documents for criteria
$group - this is opposite of $unwind in our case - so we are recreating results array
If you are expecting document like this:
{
"_id" : 2,
"od" : [{
"oid" : 1234,
"ca" : ISODate("2016-05-11T13:20:10.718Z")
}, {
"oid" : 2345,
"ca" : ISODate("2016-05-12T13:20:10.718Z")
}
]
}, {
"_id" : 1,
"od" : [{
"oid" : 2345,
"ca" : ISODate("2016-05-11T13:20:10.718Z")
}, {
"oid" : 57766,
"ca" : ISODate("2016-05-13T13:20:10.718Z")
}
]
}
you can use query bellow:
db.userDetail.aggregate([{
$match : {
"od.ca" : {
$lt : new Date(new Date().setDate(new Date().getDate() + 2)),
$gte : new Date(new Date().setDate(new Date().getDate() - 4))
}
}
}, {
$unwind : "$od"
}, {
$match : {
"od.ca" : {
$lt : new Date(new Date().setDate(new Date().getDate() + 2)),
$gte : new Date(new Date().setDate(new Date().getDate() - 4))
}
}
}, {
$group : {
_id : "$_id",
od : {
$push : "$od"
}
}
}
])
The following query gave me the desired result.
If you are using mongodb-2.6.X up to 3.0.X can use this solution.
var object = {st : "10/05/2016", et : "13/05/2016"};
db.userDetail.aggregate(
[
{
$match:
{
"od.ca":
{
'$gte': new Date(object.st),
'$lte': new Date(object.et)
},
"lo" : {$ne : 'd'}
}
},
{
$project:
{
em: 1,
fna : 1,
lna : 1,
ca :1,
od:
{
"$setDifference":
[{
"$map":
{
"input": "$od",
"as": "o",
"in":
{
"$cond":[
{
"$and":
[
{ "$gte": [ "$$o.ca", new Date(object.st) ] },
{ "$lte": [ "$$o.ca", new Date(object.et) ] },
{ "$ne": [ "$$o.oid", ObjectID(config.pid.toString())
] }
]
},
"$$o",false]
}
}
},[false]
]
}
}
},
{$sort : {_id : 1}}
];
)
If you are using 3.2.X, use $filter to get the result.

MongoDB find query using $and and $nor operator

I have a collection as below
{
"_id" : ObjectId("55bec0793bed809b2cb658ad"),
"lock_1" : ["desc_1","desc_2"],
"lock_2" : ["desc_1"]
},
{
"_id" : ObjectId("55bec0793bed809b2cb658ab"),
"lock_1" : ["desc_1","desc_2","desc_3"],
"lock_2" : ["desc_1"]
}
{
"_id" : ObjectId("55bec0793bed809b2cb658ac"),
"lock_1" : ["desc_1"],
"lock_2" : []
},
{
"_id" : ObjectId("55bec0793bed809b2cb658ae"),
"lock_1" : [],
"lock_2" : ["desc_1"]
},
{
"_id" : ObjectId("55bec0793bed809b2cb658aj"),
"lock_1" : ["desc_3","desc_4"],
"lock_2" : ["desc_5"]
},
{
"_id" : ObjectId("55bec0793bed809b2cb658ak"),
"lock_1" : [],
"lock_2" : []
}
I have retrieved all the documents having "desc_1" in both "lock_1" and "lock_2" array by using below query, returning first two documents, which is correct.
db.locks.find( { $and: [ {lock_1 : "desc_1"} , {lock_2 : "desc_1"} ] } )
Now, I am trying to get documents which does not satisfy above condition. I tried using below query but it is returning last two documents.
db.locks.find( { $nor: [ {lock_1 : "desc_1"} , {lock_2 : "desc_1"} ] } )
How to retrieve documents where "desc_1" is either not present or present in one of the arrays?
What you need here is the $or operator which performs a logical OR operation and the $ne operator to test the contents of fields directly.
db.locks.find({
"$or": [
{ "lock_1": { "$ne": "desc_1" } },
{ "lock_2": { "$ne": "desc_1" } }
]
})
db.locks.find({
$or: [
{ lock_1 : { $nin: [ "desc_1" ] }} ,
{ lock_2 : { $nin: [ "desc_1" ] }}
]
})
reference:
$or
$nin