MongoDB sum of two nested field is less than some ratio of another nested field [duplicate] - mongodb

I have a collection T, with 2 fields: Grade1 and Grade2, and I want to select those with condition Grade1 > Grade2, how can I get a query like in MySQL?
Select * from T Where Grade1 > Grade2

You can use a $where. Just be aware it will be fairly slow (has to execute Javascript code on every record) so combine with indexed queries if you can.
db.T.find( { $where: function() { return this.Grade1 > this.Grade2 } } );
or more compact:
db.T.find( { $where : "this.Grade1 > this.Grade2" } );
UPD for mongodb v.3.6+
you can use $expr as described in recent answer

You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
Regular Query:
db.T.find({$expr:{$gt:["$Grade1", "$Grade2"]}})
Aggregation Query:
db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})

If your query consists only of the $where operator, you can pass in just the JavaScript expression:
db.T.find("this.Grade1 > this.Grade2");
For greater performance, run an aggregate operation that has a $redact pipeline to filter the documents which satisfy the given condition.
The $redact pipeline incorporates the functionality of $project and $match to implement field level redaction where it will return all documents matching the condition using $$KEEP and removes from the pipeline results those that don't match using the $$PRUNE variable.
Running the following aggregate operation filter the documents more efficiently than using $where for large collections as this uses a single pipeline and native MongoDB operators, rather than JavaScript evaluations with $where, which can slow down the query:
db.T.aggregate([
{
"$redact": {
"$cond": [
{ "$gt": [ "$Grade1", "$Grade2" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
which is a more simplified version of incorporating the two pipelines $project and $match:
db.T.aggregate([
{
"$project": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] },
"Grade1": 1,
"Grade2": 1,
"OtherFields": 1,
...
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
With MongoDB 3.4 and newer:
db.T.aggregate([
{
"$addFields": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
}
},
{ "$match": { "isGrade1Greater": 1 } }
])

In case performance is more important than readability and as long as your condition consists of simple arithmetic operations, you can use aggregation pipeline. First, use $project to calculate the left hand side of the condition (take all fields to left hand side). Then use $match to compare with a constant and filter. This way you avoid javascript execution. Below is my test in python:
import pymongo
from random import randrange
docs = [{'Grade1': randrange(10), 'Grade2': randrange(10)} for __ in range(100000)]
coll = pymongo.MongoClient().test_db.grades
coll.insert_many(docs)
Using aggregate:
%timeit -n1 -r1 list(coll.aggregate([
{
'$project': {
'diff': {'$subtract': ['$Grade1', '$Grade2']},
'Grade1': 1,
'Grade2': 1
}
},
{
'$match': {'diff': {'$gt': 0}}
}
]))
1 loop, best of 1: 192 ms per loop
Using find and $where:
%timeit -n1 -r1 list(coll.find({'$where': 'this.Grade1 > this.Grade2'}))
1 loop, best of 1: 4.54 s per loop

Related

Mongo Db array two Key Compare [duplicate]

I have a collection T, with 2 fields: Grade1 and Grade2, and I want to select those with condition Grade1 > Grade2, how can I get a query like in MySQL?
Select * from T Where Grade1 > Grade2
You can use a $where. Just be aware it will be fairly slow (has to execute Javascript code on every record) so combine with indexed queries if you can.
db.T.find( { $where: function() { return this.Grade1 > this.Grade2 } } );
or more compact:
db.T.find( { $where : "this.Grade1 > this.Grade2" } );
UPD for mongodb v.3.6+
you can use $expr as described in recent answer
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
Regular Query:
db.T.find({$expr:{$gt:["$Grade1", "$Grade2"]}})
Aggregation Query:
db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})
If your query consists only of the $where operator, you can pass in just the JavaScript expression:
db.T.find("this.Grade1 > this.Grade2");
For greater performance, run an aggregate operation that has a $redact pipeline to filter the documents which satisfy the given condition.
The $redact pipeline incorporates the functionality of $project and $match to implement field level redaction where it will return all documents matching the condition using $$KEEP and removes from the pipeline results those that don't match using the $$PRUNE variable.
Running the following aggregate operation filter the documents more efficiently than using $where for large collections as this uses a single pipeline and native MongoDB operators, rather than JavaScript evaluations with $where, which can slow down the query:
db.T.aggregate([
{
"$redact": {
"$cond": [
{ "$gt": [ "$Grade1", "$Grade2" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
which is a more simplified version of incorporating the two pipelines $project and $match:
db.T.aggregate([
{
"$project": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] },
"Grade1": 1,
"Grade2": 1,
"OtherFields": 1,
...
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
With MongoDB 3.4 and newer:
db.T.aggregate([
{
"$addFields": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
In case performance is more important than readability and as long as your condition consists of simple arithmetic operations, you can use aggregation pipeline. First, use $project to calculate the left hand side of the condition (take all fields to left hand side). Then use $match to compare with a constant and filter. This way you avoid javascript execution. Below is my test in python:
import pymongo
from random import randrange
docs = [{'Grade1': randrange(10), 'Grade2': randrange(10)} for __ in range(100000)]
coll = pymongo.MongoClient().test_db.grades
coll.insert_many(docs)
Using aggregate:
%timeit -n1 -r1 list(coll.aggregate([
{
'$project': {
'diff': {'$subtract': ['$Grade1', '$Grade2']},
'Grade1': 1,
'Grade2': 1
}
},
{
'$match': {'diff': {'$gt': 0}}
}
]))
1 loop, best of 1: 192 ms per loop
Using find and $where:
%timeit -n1 -r1 list(coll.find({'$where': 'this.Grade1 > this.Grade2'}))
1 loop, best of 1: 4.54 s per loop

MongoDB: find records using the date interval calculated from item value [duplicate]

I have a collection T, with 2 fields: Grade1 and Grade2, and I want to select those with condition Grade1 > Grade2, how can I get a query like in MySQL?
Select * from T Where Grade1 > Grade2
You can use a $where. Just be aware it will be fairly slow (has to execute Javascript code on every record) so combine with indexed queries if you can.
db.T.find( { $where: function() { return this.Grade1 > this.Grade2 } } );
or more compact:
db.T.find( { $where : "this.Grade1 > this.Grade2" } );
UPD for mongodb v.3.6+
you can use $expr as described in recent answer
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
Regular Query:
db.T.find({$expr:{$gt:["$Grade1", "$Grade2"]}})
Aggregation Query:
db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})
If your query consists only of the $where operator, you can pass in just the JavaScript expression:
db.T.find("this.Grade1 > this.Grade2");
For greater performance, run an aggregate operation that has a $redact pipeline to filter the documents which satisfy the given condition.
The $redact pipeline incorporates the functionality of $project and $match to implement field level redaction where it will return all documents matching the condition using $$KEEP and removes from the pipeline results those that don't match using the $$PRUNE variable.
Running the following aggregate operation filter the documents more efficiently than using $where for large collections as this uses a single pipeline and native MongoDB operators, rather than JavaScript evaluations with $where, which can slow down the query:
db.T.aggregate([
{
"$redact": {
"$cond": [
{ "$gt": [ "$Grade1", "$Grade2" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
which is a more simplified version of incorporating the two pipelines $project and $match:
db.T.aggregate([
{
"$project": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] },
"Grade1": 1,
"Grade2": 1,
"OtherFields": 1,
...
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
With MongoDB 3.4 and newer:
db.T.aggregate([
{
"$addFields": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
In case performance is more important than readability and as long as your condition consists of simple arithmetic operations, you can use aggregation pipeline. First, use $project to calculate the left hand side of the condition (take all fields to left hand side). Then use $match to compare with a constant and filter. This way you avoid javascript execution. Below is my test in python:
import pymongo
from random import randrange
docs = [{'Grade1': randrange(10), 'Grade2': randrange(10)} for __ in range(100000)]
coll = pymongo.MongoClient().test_db.grades
coll.insert_many(docs)
Using aggregate:
%timeit -n1 -r1 list(coll.aggregate([
{
'$project': {
'diff': {'$subtract': ['$Grade1', '$Grade2']},
'Grade1': 1,
'Grade2': 1
}
},
{
'$match': {'diff': {'$gt': 0}}
}
]))
1 loop, best of 1: 192 ms per loop
Using find and $where:
%timeit -n1 -r1 list(coll.find({'$where': 'this.Grade1 > this.Grade2'}))
1 loop, best of 1: 4.54 s per loop

Mongodb aggregate match not working [duplicate]

I have a collection T, with 2 fields: Grade1 and Grade2, and I want to select those with condition Grade1 > Grade2, how can I get a query like in MySQL?
Select * from T Where Grade1 > Grade2
You can use a $where. Just be aware it will be fairly slow (has to execute Javascript code on every record) so combine with indexed queries if you can.
db.T.find( { $where: function() { return this.Grade1 > this.Grade2 } } );
or more compact:
db.T.find( { $where : "this.Grade1 > this.Grade2" } );
UPD for mongodb v.3.6+
you can use $expr as described in recent answer
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
Regular Query:
db.T.find({$expr:{$gt:["$Grade1", "$Grade2"]}})
Aggregation Query:
db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})
If your query consists only of the $where operator, you can pass in just the JavaScript expression:
db.T.find("this.Grade1 > this.Grade2");
For greater performance, run an aggregate operation that has a $redact pipeline to filter the documents which satisfy the given condition.
The $redact pipeline incorporates the functionality of $project and $match to implement field level redaction where it will return all documents matching the condition using $$KEEP and removes from the pipeline results those that don't match using the $$PRUNE variable.
Running the following aggregate operation filter the documents more efficiently than using $where for large collections as this uses a single pipeline and native MongoDB operators, rather than JavaScript evaluations with $where, which can slow down the query:
db.T.aggregate([
{
"$redact": {
"$cond": [
{ "$gt": [ "$Grade1", "$Grade2" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
which is a more simplified version of incorporating the two pipelines $project and $match:
db.T.aggregate([
{
"$project": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] },
"Grade1": 1,
"Grade2": 1,
"OtherFields": 1,
...
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
With MongoDB 3.4 and newer:
db.T.aggregate([
{
"$addFields": {
"isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
}
},
{ "$match": { "isGrade1Greater": 1 } }
])
In case performance is more important than readability and as long as your condition consists of simple arithmetic operations, you can use aggregation pipeline. First, use $project to calculate the left hand side of the condition (take all fields to left hand side). Then use $match to compare with a constant and filter. This way you avoid javascript execution. Below is my test in python:
import pymongo
from random import randrange
docs = [{'Grade1': randrange(10), 'Grade2': randrange(10)} for __ in range(100000)]
coll = pymongo.MongoClient().test_db.grades
coll.insert_many(docs)
Using aggregate:
%timeit -n1 -r1 list(coll.aggregate([
{
'$project': {
'diff': {'$subtract': ['$Grade1', '$Grade2']},
'Grade1': 1,
'Grade2': 1
}
},
{
'$match': {'diff': {'$gt': 0}}
}
]))
1 loop, best of 1: 192 ms per loop
Using find and $where:
%timeit -n1 -r1 list(coll.find({'$where': 'this.Grade1 > this.Grade2'}))
1 loop, best of 1: 4.54 s per loop

MongoDB, right projection subfield [duplicate]

Is it possible to rename the name of fields returned in a find query? I would like to use something like $rename, however I wouldn't like to change the documents I'm accessing. I want just to retrieve them differently, something that works like SELECT COORINATES AS COORDS in SQL.
What I do now:
db.tweets.findOne({}, {'level1.level2.coordinates': 1, _id:0})
{'level1': {'level2': {'coordinates': [10, 20]}}}
What I would like to be returned is:
{'coords': [10, 20]}
So basically using .aggregate() instead of .find():
db.tweets.aggregate([
{ "$project": {
"_id": 0,
"coords": "$level1.level2.coordinates"
}}
])
And that gives you the result that you want.
MongoDB 2.6 and above versions return a "cursor" just like find does.
See $project and other aggregation framework operators for more details.
For most cases you should simply rename the fields as returned from .find() when processing the cursor. For JavaScript as an example, you can use .map() to do this.
From the shell:
db.tweets.find({},{'level1.level2.coordinates': 1, _id:0}).map( doc => {
doc.coords = doc['level1']['level2'].coordinates;
delete doc['level1'];
return doc;
})
Or more inline:
db.tweets.find({},{'level1.level2.coordinates': 1, _id:0}).map( doc =>
({ coords: doc['level1']['level2'].coordinates })
)
This avoids any additional overhead on the server and should be used in such cases where the additional processing overhead would outweigh the gain of actual reduction in size of the data retrieved. In this case ( and most ) it would be minimal and therefore better to re-process the cursor result to restructure.
As mentioned by #Neil Lunn this can be achieved with an aggregation pipeline:
And starting Mongo 4.2, the $replaceWith aggregation operator can be used to replace a document by a sub-document:
// { level1: { level2: { coordinates: [10, 20] }, b: 4 }, a: 3 }
db.collection.aggregate(
{ $replaceWith: { coords: "$level1.level2.coordinates" } }
)
// { "coords" : [ 10, 20 ] }
Since you mention findOne, you can also limit the number of resulting documents to 1 as such:
db.collection.aggregate([
{ $replaceWith: { coords: "$level1.level2.coordinates" } },
{ $limit: 1 }
])
Prior to Mongo 4.2 and starting Mongo 3.4, $replaceRoot can be used in place of $replaceWith:
db.collection.aggregate(
{ $replaceRoot: { newRoot: { coords: "$level1.level2.coordinates" } } }
)
As we know, in general, $project stage takes the field names and specifies 1 or 0/true or false to include the fields in the output or not, we also can specify the value against a field instead of true or false to rename the field. Below is the syntax
db.test_collection.aggregate([
{$group: {
_id: '$field_to_group',
totalCount: {$sum: 1}
}},
{$project: {
_id: false,
renamed_field: '$_id', // here assigning a value instead of 0 or 1 / true or false effectively renames the field.
totalCount: true
}}
])
Stages (>= 4.2)
$addFields : {"New": "$Old"}
$unset : {"$Old": 1}

How to add order in $all operator in MongoDB

How can I get documents from mongo with an array containing some elements but IN THE SAME ORDER?
I know that $all do the job but ignoring the order of elements. The order in my case is important and I can't sort my arrays since it's describing a path that I want to keep the order.
111,222,333 is not the same as 222,111,333
Is there a way to do it using $all or maybe another operator in mongo aggregation framework?
You can avoid the first "intersect" field, is just to give you back as debug what MongoDB make with this command. You should create the $and operator dynamically.
db.Test6.aggregate([
{
$project: {
_id:1,
pages:1,
intersect: {$setIntersection: [[111,666], "$pages"]},
theCondition: {$let: {
vars: {
intersect: {$setIntersection: [[111,666], "$pages"]}
},
in: {
$cond:[ {$and:[
{$eq:[{$arrayElemAt:["$$intersect", 0]}, 111]},
{$eq:[{$arrayElemAt:["$$intersect", 1]}, 666]}
]} , true, false]
}
}
}
}
}
]);