Условие запроса MongoDb при сравнении 2 полей

108

У меня есть коллекция Tс двумя полями: Grade1и Grade2, и я хочу выбрать те, у которых есть условие Grade1 > Grade2, как я могу получить запрос, как в MySQL?

Select * from T Where Grade1 > Grade2
Диего Ченг
источник

Ответы:

120

Вы можете использовать $ where. Просто имейте в виду, что это будет довольно медленно (необходимо выполнять код Javascript для каждой записи), поэтому комбинируйте с индексированными запросами, если можете.

db.T.find( { $where: function() { return this.Grade1 > this.Grade2 } } );

или более компактный:

db.T.find( { $where : "this.Grade1 > this.Grade2" } );

UPD для mongodb v.3.6 +

вы можете использовать, $exprкак описано в недавнем ответе

Ян
источник
Ой, я понял, просто совместное использование javascript или других сценариев оболочки, спасибо вам обоим!
Диего Ченг,
16
Вы также можете сделать это немного компактнее ...> db.T.find ({$ where: "this.Grade1> this.Grade2"});
Джастин Дженкинс,
как я мог $where: function() { return this.Grade1 - this.Grade2 > variable }?
Луис Гонсалес,
когда я попробовал, db.T.find({$where: function() {return this.startDate == ISODate("2017-01-20T10:55:08.000Z");}});он ничего не возвращает, даже один из документов в коллекции ISODate("2017-01-20T10:55:08.000Z"). Но <=и >=похоже работают. любая идея?
cateyes
@cateyes Возможно, немного поздно ... но чистый javascript всегда возвращает false при выполнении == сравнения между двумя датами. Однако Mongo позволяет искать точные совпадения между датами в запросах. Один способ обхода - использование .getTime () для преобразования в миллисекунды или что-то еще:this.startDate.getTime() == ISODate("2017-01-20T10:55:08.000Z").getTime()
leinaD_natipaC
53

Вы можете использовать $ expr (оператор версии 3.6 mongo) для использования функций агрегирования в обычном запросе.

Сравнить query operatorsпротив aggregation comparison operators.

Обычный запрос:

db.T.find({$expr:{$gt:["$Grade1", "$Grade2"]}})

Агрегационный запрос:

db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})
SVR
источник
39

Если ваш запрос состоит только из $whereоператора, вы можете передать только выражение JavaScript:

db.T.find("this.Grade1 > this.Grade2");

Для повышения производительности запустите агрегатную операцию с $redactконвейером для фильтрации документов, удовлетворяющих заданному условию.

$redactТрубопровод включает в себя функциональные возможности $projectи $matchреализовать на уровне поля , где редакцию будет возвращать все документы , соответствующие условиям использования $$KEEPи удаляет из результатов трубопроводов те , которые не совпадают с помощью $$PRUNEпеременной.


Выполнение следующей агрегатной операции фильтрует документы более эффективно, чем использование $whereдля больших коллекций, поскольку при этом используется один конвейер и собственные операторы MongoDB, а не оценки JavaScript $where, что может замедлить запрос:

db.T.aggregate([
    {
        "$redact": {
            "$cond": [
                { "$gt": [ "$Grade1", "$Grade2" ] },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

что является более упрощенной версией объединения двух конвейеров $projectи $match:

db.T.aggregate([
    {
        "$project": {
            "isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] },
            "Grade1": 1,
            "Grade2": 1,
            "OtherFields": 1,
            ...
        }
    },
    { "$match": { "isGrade1Greater": 1 } }
])

С MongoDB 3.4 и новее:

db.T.aggregate([
    {
        "$addFields": {
            "isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
        }
    },
    { "$match": { "isGrade1Greater": 1 } }
])
Chridam
источник
последний, похоже, не работает со мной. Поле isGrade1Greater добавляется и оценивается правильно, но по какой-то причине запрос соответствует всем строкам независимо от значения isGrade1Greater. Что могло быть причиной такого поведения? РЕДАКТИРОВАТЬ: Неважно, я не передавал массив в aggregate (), а каждую агрегацию в качестве самого параметра пропустил.
ThatBrianDude
13

Если производительность важнее удобочитаемости и если ваше условие состоит из простых арифметических операций, вы можете использовать конвейер агрегации. Во-первых, используйте $ project, чтобы вычислить левую часть условия (перенесите все поля в левую часть). Затем используйте $ match для сравнения с константой и фильтра. Таким образом вы избегаете выполнения javascript. Ниже мой тест на питоне:

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)

Используя агрегат:

%timeit -n1 -r1 list(coll.aggregate([
    {
        '$project': {
            'diff': {'$subtract': ['$Grade1', '$Grade2']},
            'Grade1': 1,
            'Grade2': 1
        }
    },
    {
        '$match': {'diff': {'$gt': 0}}
    }
]))

1 цикл, лучшее из 1: 192 мс на цикл

Используя find и $ where:

%timeit -n1 -r1 list(coll.find({'$where': 'this.Grade1 > this.Grade2'}))

1 петля, лучше всего 1: 4,54 с на петлю

Сина
источник