Using $not in mongodb - mongodb

I'm trying to do something like this :
select * from table where not (a=3 and b=4 and c=3 or x=4)
I would expect this to work:
db.table.find({
$not: {
$or: [
{
$and: [
{ a: 3 },
{ b: 4 },
{ c: 3 }
]
},
{ x: 4 }
]
}
})
But it gives me an error:
error: { "$err" : "invalid operator: $and", "code" : 10068 }
Is there another way to express it in mongodb?

Firstly, I don't think you mean "and" as a field will never 3 and 4 at the same time - it can only be 3 or 4. So, assuming you do want "documents where b is not 3 or 4, then you can use $nin (not in) like this:
db.table.find({b:{$nin: [3,4]}});

Using { $not : { $and : []} } will not work ($not is not like other operators, can only be applied to negate the check of other operators).
$and is not the problem here, this also doesn't work (though without reporting any errors):
{ $not : { a : {$gt : 14} }
you'd have to rewrite it to
{ a : { $not : {$gt : 14} }
Coming back to your query:
`not (a=3 and b=4 and c=3 or x=4)`
is equivalent to:
a!=3 and b!=4 and c!=3 or x!=4
and that you can do in mongo:
{a : { $ne : 3}, b : { $ne : 4} ...}

Related

Sort inside cond and if mongodb

I want to sort my aggregation only if a condition is met.
This is what I have so far:
{
$cond: {
if: { $gte: [sort, "like"] },
then: { $divide: { $sort : { total_likes : -1 } } },
else: { $divide: '' }
}
}
sort is a variable that comes from a query parameter.
I want to sort by total_likes, only if sort is "likes". If it's not, I want to leave it alone.
First of all, #schoenbl, if you want to match some condition in mongo aggregation, you should use $match aggregation. It will send the documents which fulfill the given condition.
if: { $gte: [sort, "like"] }
In MongoDB, you are not allowed to compare string using "gte" operator. For string comparison in MongoDB, you get two operators:
for case sensitive $cmp.
for case insensitive $strcasecmp.
then: { $divide: { $sort : { total_likes : -1 } } },
Next, you were using divide operator don't know what is your need but syntax is improper,
refer $divide, for better knowledge.
Also, you are doing sorting in $cond, which means you want to sort each element, and that is not possible because you can't sort without having a comparison as you are inside $cond operator and it is performing manipulation on a single document.
Now, according to your need, I have prepared the next stages which will give sorted document which contains "sort" equals to "like".
{$match:{"sort":"like"}},{$sort:{"total_likes":-1}}
Output:
{ "_id" : ObjectId("5d50569fbe39828b4a22fba2"), "name" : "kyle", "sort" : "like", "total_likes" : 5 }
{ "_id" : ObjectId("5d5056a6be39828b4a22fba3"), "name" : "jack", "sort" : "like", "total_likes" : 2 }
{ "_id" : ObjectId("5d5056abbe39828b4a22fba4"), "name" : "john", "sort" : "like", "total_likes" : 1 }

Mongodb regex in aggregation using reference to field value

note: I'm using Mongodb 4 and I must use aggregation, because this is a step of a bigger aggregation
Problem
How to find in a collection documents that contains fields that starts with value from another field in same document ?
Let's start with this collection:
db.regextest.insert([
{"first":"Pizza", "second" : "Pizza"},
{"first":"Pizza", "second" : "not pizza"},
{"first":"Pizza", "second" : "not pizza"}
])
and an example query for exact match:
db.regextest.aggregate([
{
$match : { $expr: { $eq: [ "$first" ,"$second" ] } } }
])
I will get a single document
{
"_id" : ObjectId("5c49d44329ea754dc48b5ace"),
"first" : "Pizza", "second" : "Pizza"
}
And this is good.
But how to do the same, but with startsWith ? My plan was to use regex but seems that is not supported in aggregation so far.
With a find and a custom javascript function works fine:
db.regextest.find().forEach(
function(obj){
if (obj.first.startsWith(obj.second)){
print(obj);
}
}
)
And returns correctly:
{
"_id" : ObjectId("5c49d44329ea754dc48b5ace"),
"first" : "Pizza",
"second" : "Pizza"
}
How it's possible to get same result with aggregation framework ?
One idea is to use existing aggregation framework pipeline, out to a temp colletion and then run the find above, to get match I'm looking for. This seems to be a workaround, I hope someone have a better idea.
Edit: here the solution
db.regextest.aggregate([{
$project : {
"first" : 1,
"second" : 1,
fieldExists : {
$indexOfBytes : ['$first', '$second' , 0]
}
}
}, {
$match : {
fieldExists : {
$gt : -1
}
}
}
]);
The simplest way is to use $expr, first available in 3.6 like this:
{
$match: {
$expr: {
$eq: [
'$second',
{
$substr: ['$first', 0, { $strLenCP: '$second' }]
}
]
}
}
}
This compares the string in field second with the first N characters of first where N is the length of second string. If they are equal, then first starts with second.
4.2 adds support for $regex in aggregation expressions, but starts with is much simpler and doesn't need regular expressions.

mongodb apply $not to $and - is there some ideas?

I'm trying to do something like this :
select * from table where not (a=3 and b=4 and c=3 or x=4)
I would expect this to work:
db.table.find( {
$not : {
$or : [
{ $and : [ { a : 3 },
{ b : 4 },
{ c : 3 }
] } ,
{ x : 4 }
}
} )
But it is not work.
I have read this article
And something like this: {a : { $ne : 3}, b : { $ne : 4} ...}. does
not suit me.
Because: my program takes DIFFERENT queries like this (a=3 and b=4 and c=3 or x=4) from users (queries to multilevel embedded objects and arrays).
And to write procedure, wich automatically apply $not to those queries looks
long and thankless task. have you any ideas?
PS Why mongo does not have the way simply to do that?
for example, to find all the documents that match the condition, and to take from the collection of the remaining documents

How to use "more" and less in query?

I have a simple mongodb collection:
{
"_id" : { "id" : "3CE33FCC-AFB1-F59A-2839-3D151DB95A6B" },
"value" : { "count" : 2 }
}
Why this query
db.testb.find({ "value" : { "count" : { $gt: 1 } } })
doesn't work ?
You can use dot notation to access sub documents in mongoDB
This should work:
db.testb.find({ "value.count" : { $gt: 1 } });
In mongodb there is so known dot notation, that can be used to reach into objects and arrays.
Workable query according to dot notation will looks like this:
db.testb.find({ "value.count" : { $gt: 1 } })

mongodb $elemMatch

According to mongodb doc, syntax for $elemMatch would be,
t.find( { x : { $elemMatch : { a : 1, b : { $gt : 1 } } } } )
I have tried and it works fine.
The above means that, it can find if an object {a:1, b:'more than 1'} exist in the array x.
I have a requirement, where I need to figure out, if all the objects in an array exist in the database or not.
for example, let's say I have an array,
a=[{a:1, b:2},{a:3, b:4}, {a:5, b:6}]
and I need to find out if x contains all of them.
t.find( { x : { $elemMatch : { a : {$all:[1]}, b : {$all:[2]} } } } ) and it finds out all x containing {a:1, b:2}
But if I try, t.find( { x : { $elemMatch : { a : {$all:[1,3]}, b : {$all:[2,4]} } } } ), it fails. I know this is not correct.
Is there any way I can achieve this ?
Ideallt, it should be,
t.find( { x : { $elemMatch : {$all:[ {a:1, b:2}, {a:3, b:4}] } } )
I tried, it does not work.
t.find({$and:[{a:{$elemMatch:{a:1, b:2}}}, {a:{$elemMatch:{a:3, b:4}}}, {a:{$elemMatch:{a:5, b:6}}}]})
It isn't a particularly high performance option though.
You can not use elemMatch for this, but you can simply just create a query which checks whether a matches the whole array:
db.items.insert({ 'foo' : 1, 'a' : [{a:1, b:2},{a:3, b:4}, {a:5, b:6}]});
db.items.insert({ 'foo' : 1, 'a' : [{a:1, b:2},{a:3, b:4}, {a:8, b:7}]});
db.items.find({'a': [{a:1, b:2},{a:3, b:4}, {a:8, b:7}]});
{ "_id" : ObjectId("4f3391856e196eca5eaa7518"), "foo" : 1, "a" : [ { "a" : 1, "b" : 2 }, { "a" : 3, "b" : 4 }, { "a" : 8, "b" : 7 } ] }
However, for this to work the order of the elements in the array need to be the same for the document and the query. The following will not find anything:
db.items.find({'a': [{a:3, b:4},{a:1, b:2}, {a:8, b:7}]});
(Because {a:3, b:4} and {a:1, b:2} are swapped).
How about this:
db.items.find({x : { $all: [
{$elemMatch: {a: 1, b: 2}},
{$elemMatch: {a: 3, b: 4}},
{$elemMatch: {a: 5, b: 6}}
]}}
Check out the Mongo docs.
Also, note the docs warning:
In the current release, queries that use the $all operator must scan
all the documents that match the first element in the query array. As
a result, even with an index to support the query, the operation may
be long running, particularly when the first element in the array is
not very selective.