Condição de consulta MongoDb ao comparar 2 campos

108

Eu tenho uma coleção T, com 2 campos: Grade1e Grade2, e quero selecionar aqueles com condição Grade1 > Grade2, como posso obter uma consulta como no MySQL?

Select * from T Where Grade1 > Grade2
Diego Cheng
fonte

Respostas:

120

Você pode usar um $ where. Esteja ciente de que será bastante lento (precisa executar o código Javascript em todos os registros), portanto, combine com as consultas indexadas, se puder.

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

ou mais compacto:

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

UPD para mongodb v.3.6 +

você pode usar $exprconforme descrito na resposta recente

Ian
fonte
Opa, entendi, basta usar javascript ou outros scripts de shell, obrigado a ambos!
Diego Cheng
16
Você pode fazer isso um pouco mais compacto também ...> db.T.find ({$ where: "this.Grade1> this.Grade2"});
Justin Jenkins
como eu poderia $where: function() { return this.Grade1 - this.Grade2 > variable }?
Luis González
quando tentei, db.T.find({$where: function() {return this.startDate == ISODate("2017-01-20T10:55:08.000Z");}});não retornou nada, até mesmo um dos documentos da coleção ISODate("2017-01-20T10:55:08.000Z"). Mas <=e >=parece funcionar. qualquer ideia?
cateyes
@cateyes Talvez um pouco tarde ... mas o javascript puro sempre retorna falso ao fazer uma == comparação entre 2 datas. O Mongo, no entanto, permite pesquisar correspondências exatas entre as datas nas consultas. Uma solução alternativa é usar .getTime () para converter para milissegundos ou qualquer outra coisa:this.startDate.getTime() == ISODate("2017-01-20T10:55:08.000Z").getTime()
leinaD_natipaC
53

Você pode usar $ expr (operador de versão 3.6 mongo) para usar funções de agregação em consultas regulares.

Compare query operatorsvs aggregation comparison operators.

Consulta regular:

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

Consulta de agregação:

db.T.aggregate({$match:{$expr:{$gt:["$Grade1", "$Grade2"]}}})
svr
fonte
39

Se sua consulta consistir apenas no $whereoperador, você pode passar apenas a expressão JavaScript:

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

Para maior desempenho, execute uma operação agregada que tenha um $redactpipeline para filtrar os documentos que satisfaçam a determinada condição.

O $redactpipeline incorpora a funcionalidade de $projecte $matchpara implementar a redação no nível do campo, onde retornará todos os documentos que correspondem à condição usando $$KEEPe remove dos resultados do pipeline aqueles que não correspondem ao uso da $$PRUNEvariável.


Executar a operação de agregação a seguir filtra os documentos com mais eficiência do que usar $wherepara grandes coleções, já que usa um único pipeline e operadores MongoDB nativos, em vez de avaliações de JavaScript com $where, o que pode tornar a consulta mais lenta:

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

que é uma versão mais simplificada de incorporar os dois pipelines $projecte $match:

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

Com MongoDB 3.4 e mais recente:

db.T.aggregate([
    {
        "$addFields": {
            "isGrade1Greater": { "$cmp": [ "$Grade1", "$Grade2" ] }
        }
    },
    { "$match": { "isGrade1Greater": 1 } }
])
chridam
fonte
o último não parece funcionar comigo. O campo isGrade1Greater é adicionado e avaliado corretamente, mas por algum motivo a consulta corresponde a todas as linhas, independentemente do valor de isGrade1Greater. Qual pode ser a causa desse comportamento? EDIT: Não importa, eu não estava passando um array para aggregate (), mas sim cada agregação como um parâmetro em si, perdi isso.
ThatBrianDude
13

Caso o desempenho seja mais importante do que a legibilidade e contanto que sua condição consista em operações aritméticas simples, você pode usar o pipeline de agregação. Primeiro, use $ project para calcular o lado esquerdo da condição (leve todos os campos para o lado esquerdo). Em seguida, use $ match para comparar com uma constante e um filtro. Desta forma, você evita a execução de javascript. Abaixo está meu teste em 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)

Usando agregado:

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

1 loop, melhor de 1: 192 ms por loop

Usando find e $ where:

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

1 loop, melhor de 1: 4,54 s por loop

Sina
fonte