154

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
alaster
  • 3,821
  • 3
  • 24
  • 32
Diego Cheng
  • 1,793
  • 3
  • 12
  • 7

4 Answers4

161

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

Community
  • 1
  • 1
Ian
  • 1,650
  • 1
  • 11
  • 5
  • Oops, I get it, just joint-use javascript or other shell scripts, thanks both of you guys! – Diego Cheng Dec 14 '10 at 19:59
  • 17
    You can do this a little more compact too ... > db.T.find({ $where : "this.Grade1 > this.Grade2" }); – Justin Jenkins Dec 14 '10 at 20:03
  • how could I `$where: function() { return this.Grade1 - this.Grade2 > variable }` ? – Luis González May 27 '16 at 14:46
  • when i tried `db.T.find({$where: function() {return this.startDate == ISODate("2017-01-20T10:55:08.000Z");}});` it returns nothing, even one of the doc in the collection is `ISODate("2017-01-20T10:55:08.000Z")`. But `<=` and `>=` seem work. any idea? – cateyes Feb 22 '17 at 22:51
  • @cateyes A bit late perhaps... but pure javascript always returns false when doing an == compare between 2 dates. Mongo, however, lets you search for exact matches between dates in queries. One workaround is using .getTime() to convert to milliseconds or whatever: `this.startDate.getTime() == ISODate("2017-01-20T10:55:08.000Z").getTime()` – leinaD_natipaC Oct 01 '19 at 10:28
129

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"]}}})
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Is there any benefit of using `$expr` over `$where`? – Akaisteph7 Sep 26 '22 at 14:08
  • 2
    @Akaisteph7 `$expr` is the preferred operator. Check [documentation of $where](https://www.mongodb.com/docs/manual/reference/operator/query/where/), it lists many limitations and it is slower than `$expr`. – Wernfried Domscheit Feb 11 '23 at 11:05
48

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 } }
])
chridam
  • 100,957
  • 23
  • 236
  • 235
  • the last one doesnt seem to work with me. The isGrade1Greater field is properly added and evaluated but for some reason the query matches all rows no matter the value of isGrade1Greater. What could be the cause of this behaviour? EDIT: Never mind, I wasnt passing an array to aggregate() but rather each aggregation as a parameter itself, missed that. – ThatBrianDude Aug 24 '17 at 14:02
17

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

Sina
  • 1,888
  • 1
  • 17
  • 16