Query MongoDB with $and and Multiple $or - mongodb

As stated in the documentation, this is not possible.
AND Queries With Multiple Expressions Specifying the Same Operator
Consider the following example:
db.inventory.find( {
$and : [
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
]
} )
This query will return all select all documents where:
the price field value equals 0.99 or 1.99, and
the sale field value is equal to true or the qty field value is less than 20.
This query cannot be constructed using an implicit AND operation, because it uses the $or operator more than once.
What is a workaround to query something like this? This query returns no results on MongoDB 3.2. I have tested the $or blocks separately and they are working fine, but not when they are wrapped in $and block. I assumed I didn't read the documentation incorrectly that this is not supposed to work. The only alternative I have is to push the data to ElasticSearch and query it there instead, but that's also just a workaround.
{
"$and": [
{
"$or": [
{
"title": {
"$regex": "^.*html .*$",
"$options": "i"
}
},
{
"keywords": {
"$regex": "^.*html .*$",
"$options": "i"
}
}
]
},
{
"$or": [
{
"public": true
},
{
"domain": "cozybid"
}
]
}
]
}

the documentation doesn't say that this is impossible. It only says
This query cannot be constructed using an implicit AND operation,
because it uses the $or operator more than once.
this means that this will work :
db.inventory.find( {
$and : [
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
]
} )
but this won't, because it's an implicit $and with two $or
db.inventory.find({
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
})
try it online: mongoplayground.net/p/gL_0gKzGA-u
Here is a working case with an implicit $and:
db.inventory.find({ price: { $ne: 1.99, $exists: true } })
I guess the problem you're facing is that there is no document matching your request in your collection

Related

MongoDB $cond with embedded document array

I am trying to generate a new collection with a field 'desc' having into account a condition in field in a documment array. To do so, I am using $cond statement
The origin collection example is the next one:
{
"_id" : ObjectId("5e8ef9a23e4f255bb41b9b40"),
"Brand" : {
"models" : [
{
"name" : "AA"
},
{
"name" : "BB"
}
]
}
}
{
"_id" : ObjectId("5e8ef9a83e4f255bb41b9b41"),
"Brand" : {
"models" : [
{
"name" : "AG"
},
{
"name" : "AA"
}
]
}
}
The query is the next:
db.runCommand({
aggregate: 'cars',
'pipeline': [
{
'$project': {
'desc': {
'$cond': {
if: {
$in: ['$Brand.models.name',['BB','TC','TS']]
},
then: 'Good',
else: 'Bad'
}
}
}
},
{
'$project': {
'desc': 1
}
},
{
$out: 'cars_stg'
}
],
'allowDiskUse': true,
})
The problem is that the $cond statement is always returning the "else" value. I also have tried $or statement with $eq or the $and with $ne, but is always returning "else".
What am I doing wrong, or how should I fix this?
Thanks
Since $Brand.models.name returns an array, we cannot use $in operator.
Instead, we can use $setIntersection which returns an array that contains the elements that appear in every input array
db.cars.aggregate([
{
"$project": {
"desc": {
"$cond": [
{
$gt: [
{
$size: {
$setIntersection: [
"$Brand.models.name",
[
"BB",
"TC",
"TS"
]
]
}
},
0
]
},
"Good",
"Bad"
]
}
}
},
{
"$project": {
"desc": 1
}
},
{
$out: 'cars_stg'
}
])
MongoPlayground | Alternative $reduce

Store expression/condition in document

I'm new to mongodb and am unable to figure out if the below is even possible.
Can I store a condition, such as gt, lt, etc., inside a field in a document and then utilize it in a query?
So, given the following documents:
{
title : "testTitle1",
score : 55,
condition : "gt"
}
{
title : "testTitle2",
score : 30,
condition : "lt"
}
And given an inputScore = 75, only testTitle1 document would be returned because 75 is greater than ("gt") 55. The second document would not be returned because it is not less than ("lt") 30.
Any ideas?
Thanks
Yes, you can the params passed to the query is simply an object. You will need to use the mongodb operators unless you want to add additional processing before you send the query.
As an example:
const data = {
title : "testTitle1",
score : 55,
condition : "$gt"
};
const query = {
score: {}
};
query.score[data.condition] = data.score;
console.log(query);
Example with additional operator conversion:
const data = {
title : "testTitle1",
score : 55,
condition : "gt"
};
const query = {
score: {}
};
switch (data.condition) {
case "gt":
query.score["$gt"] = data.score;
break;
case "lt":
query.score["$lt"] = data.score;
break;
}
console.log(query);
if you want pure mongodb query, you can use $expr operator in your query or aggregation.
db.collection.find({
$expr: {
$or: [
{
$and: [
{$eq: ['$condition', 'gt']},
{$gt: [75, '$score']}
]
},
{
$and: [
{$eq: ['$condition', 'lt']},
{$lt: [75, '$score']}
]
}
]
}
})
i hope you get the idea
The query is with aggregation framework. I use the $switch for checking if the conditionhas a gt or lt value and return a trueor false after the inputScore is compared with the score. This compared result matches gives the filter condition to include or reject the input document.
var inputScore = 75;
db.gtlt.aggregate( [
{ $addFields: {
matches: {
$switch: {
branches: [
{
case: { $eq : [ "$condition", "gt" ] },
then: { $cond: [ { $gt: [ inputScore, "$score" ] }, true, false ] }
},
{
case: { $eq : [ "$condition", "lt" ] },
then: { $cond: [ { $lt: [ inputScore, "$score" ] }, true, false ] }
},
],
default: false
}
}
} },
{ $match: { matches: true } },
{ $project: { matches: 0 } }
] )

Find MongoDB object using value of another field

I recently found difficulty in finding an object stored in a document with its key in another field of that same document.
{
list : {
"red" : 397n8,
"blue" : j3847,
"pink" : 8nc48,
"green" : 983c4,
},
result : [
{ "id" : 397n8, value : "anger" },
{ "id" : j3847, value : "water" },
{ "id" : 8nc48, value : "girl" },
{ "id" : 983c4, value : "evil" }
]
}
}
I am trying to get the value for 'blue' which has an id of 'j3847' and a value of 'water'.
db.docs.find( { result.id : list.blue }, { result.value : 1 } );
# list.blue would return water
# list.pink would return girl
# list.green would return evil
I tried many things and even found a great article on how to update a value using a value in the same document.: Update MongoDB field using value of another field which I based myself on; with no success... :/
How can I find a MongoDB object using value of another field ?
You can do it with the $filter operator within mongo aggregation. It returns an array with only those elements that match the condition:
db.docs.aggregate([
{
$project: {
result: {
$filter: {
input: "$result",
as:"item",
cond: { $eq: ["$list.blue", "$$item.id"]}
}
}
}
}
])
Output for this query looks like this:
{
"_id" : ObjectId("569415c8299692ceedf86573"),
"result" : [ { "id" : "j3847", "value" : "water" } ]
}
One way is using the $where operator though would not recommend as using it invokes a full collection scan regardless of what other conditions could possibly use an index selection and also invokes the JavaScript interpreter over each result document, which is going to be considerably slower than native code.
That being said, use the alternative .aggregate() method for this type of comparison instead which is definitely the better option:
db.docs.aggregate([
{ "$unwind": "$result" },
{
"$project": {
"result": 1,
"same": { "$eq": [ "$list.blue", "$result.id" ] }
}
},
{ "$match": { "same": true } },
{
"$project": {
"_id": 0,
"value": "$result.value"
}
}
])
When the $unwind operator is applied on the result array field, it will generate a new record for each and every element of the result field on which unwind is applied. It basically flattens the data and then in the subsequent $project step inspect each member of the array to compare if the two fields are the same.
Sample Output
{
"result" : [
{
"value" : "water"
}
],
"ok" : 1
}
Another alternative is to use the $map and $setDifference operators in a single $project step where you can avoid the use of $unwind which can be costly on very large collections and in most cases result in the 16MB BSON limit constraint:
db.docs.aggregate([
{
"$project": {
"result": {
"$setDifference": [
{
"$map": {
"input": "$result",
"as": "r",
"in": {
"$cond": [
{ "$eq": [ "$$r.id", "$list.blue" ] },
"$$r",
false
]
}
}
},
[false]
]
}
}
}
])
Sample Output
{
"result" : [
{
"_id" : ObjectId("569412e5a51a6656962af1c7"),
"result" : [
{
"id" : "j3847",
"value" : "water"
}
]
}
],
"ok" : 1
}

Using MongoDB's positional operator $ in a deeply nested document query

Is it possible to use positional operator '$' in combination with a query on a deeply-nested document array?
Consider the following nested document defining a 'user':
{
username: 'test',
kingdoms: [
{
buildings: [
{
type: 'castle'
},
{
type: 'treasury'
},
...
]
},
...
]
}
We'd like to return the 'castles' for a particular user e.g. in a form:
{
kingdoms: [{
buildings: [{
type: 'castle'
}]
}]
}
Because you cannot use the $ operator twice (https://jira.mongodb.org/browse/server-831) I know that I can't also query for a particular kingdom, so I'm trying to write a find statement for the nth kingdom.
This seems to make sense when updating a deeply-nested sub-document (Mongodb update deeply nested subdocument) but I'm having less success with the find query.
I can return the first kingdom's buildings with the query:
db.users.findOne(
{ username: 'test' },
{ kingdoms: {$slice: [0, 1]}, 'kingdom.buildings': 1 }
);
But this returns all the buildings of that kingdom.
Following the single-level examples of position operator I'm trying a query like this:
db.users.findOne(
{ username: 'test', 'kingdoms.buildings.type': 'castle' },
{ kingdoms: {$slice: [n, 1]}, 'kingdom.buildings.$': 1 }
);
so as to be in the form:
db.collection.find( { <array.field>: <value> ...}, { "<array>.$": 1 } )
as described in the documentation http://docs.mongodb.org/manual/reference/operator/projection/positional/#proj.S
However this fails with the error:
Positional operator does not match the query specifier
Presumably because kingdoms.buildings isn't considered an array. I've also tried kingdoms.0.buildings
It is confusing because this appears to work for updates (according to Mongodb update deeply nested subdocument)
Have I just got the syntax wrong or is this not supported? If so is there a way to achieve something similar?
You get an error from
db.users.findOne(
{ username: 'test', 'kingdoms.buildings.type': 'castle' },
{ kingdoms: {$slice: [n, 1]}, 'kingdom.buildings.$': 1 }
);
because there is a spelling mistake ("kingdom.buildings.$" should be "kingdoms.buildings.$").
However, this way can not accomplish what you expect.
$ is always aimed at kingdoms in the path of kingdoms.buildings - the first array.
This is a way that should be able to solve the problem.
(V2.6+ required)
db.c.aggregate([ {
$match : {
username : 'test',
'kingdoms.buildings.type' : 'castle'
}
}, {
$project : {
_id : 0,
kingdoms : 1
}
}, {
$redact : {
$cond : {
"if" : {
$or : [ {
$gt : [ "$kingdoms", [] ]
}, {
$gt : [ "$buildings", [] ]
}, {
$eq : [ "$type", "castle" ]
} ]
},
"then" : "$$DESCEND",
"else" : "$$PRUNE"
}
}
} ]).pretty();
To only reserve the first element of kingdoms,
db.c.aggregate([ {
$match : {
username : 'test',
'kingdoms.buildings.type' : 'castle'
}
}, {
$redact : {
$cond : {
"if" : {
$or : [ {
$gt : [ "$kingdoms", [] ]
}, {
$gt : [ "$buildings", [] ]
}, {
$eq : [ "$type", "castle" ]
} ]
},
"then" : "$$DESCEND",
"else" : "$$PRUNE"
}
}
}, {
$unwind : "$kingdoms"
}, {
$group : {
_id : "$_id",
kingdom : {
$first : "$kingdoms"
}
}
}, {
$group : {
_id : "$_id",
kingdoms : {
$push : "$kingdom"
}
}
}, {
$project : {
_id : 0,
kingdoms : 1
}
} ]).pretty();

MongoDB use $and with $or operator in the same query

Hi I want to set filter to collection on mongodb.
I want to do set filter (Code field startswith "0" or "2") and (FirmId eq to "5186224fdff7421bd4552f7f")
But query result is null. What is wrong?
My mongo query is;
db.Customers.find({ "$and" : [{ "FirmId" : ObjectId("5186224fdff7421bd4552f7f") }, { "$or" : [{ "Code" : /^0/ }, { "Code" : /^2/ }] }] })
_
{
$and: [
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f")
},
{
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}
]
}
or use
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f"),
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}
You should not be using either $and nor $or here.
{ FirmId: ObjectId("xxx"), Code:/^[02]/ }
If you don't get any results then possibly no records match this criteria.
In this case you don't need to specify global $and, the outermost object will act as if there was one.
The following query should work:
{
"FirmId": ObjectId("5186224fdff7421bd4552f7f"),
"$or": [
{
"Code": /^0/
},
{
"Code": /^2/
}
]
}