Condition de requête MongoDb sur la comparaison de 2 champs


108

J'ai une collection T, avec 2 champs: Grade1et Grade2, et je veux sélectionner ceux avec condition Grade1 > Grade2, comment puis-je obtenir une requête comme dans MySQL?

Select * from T Where Grade1 > Grade2

Réponses:


120

Vous pouvez utiliser un $ where. Sachez simplement que ce sera assez lent (doit exécuter du code Javascript sur chaque enregistrement), alors combinez-le avec des requêtes indexées si vous le pouvez.

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

ou plus compact:

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

UPD pour mongodb v.3.6 +

vous pouvez utiliser $exprcomme décrit dans la réponse récente


Oups, je comprends, il suffit d'utiliser du javascript ou d'autres scripts shell en commun, merci à vous deux!
Diego Cheng

16
Vous pouvez également le faire un peu plus compact ...> db.T.find ({$ where: "this.Grade1> this.Grade2"});
Justin Jenkins

comment pourrais-je $where: function() { return this.Grade1 - this.Grade2 > variable }?
Luis González

quand j'ai essayé, db.T.find({$where: function() {return this.startDate == ISODate("2017-01-20T10:55:08.000Z");}});il ne renvoie rien, même l'un des doc de la collection l'est ISODate("2017-01-20T10:55:08.000Z"). Mais <=et >=semblent fonctionner. une idée?
cateyes

@cateyes Un peu tard peut-être ... mais le javascript pur renvoie toujours false lors d'une comparaison == entre 2 dates. Mongo, cependant, vous permet de rechercher des correspondances exactes entre les dates dans les requêtes. Une solution de contournement consiste à utiliser .getTime () pour convertir en millisecondes ou autre:this.startDate.getTime() == ISODate("2017-01-20T10:55:08.000Z").getTime()
leinaD_natipaC

53

Vous pouvez utiliser $ expr (opérateur de version 3.6 mongo) pour utiliser les fonctions d'agrégation dans une requête normale.

Comparez query operatorsvs aggregation comparison operators.

Requête régulière:

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

Requête d'agrégation:

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

39

Si votre requête consiste uniquement en $where opérateur, vous pouvez transmettre uniquement l'expression JavaScript:

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

Pour de meilleures performances, exécutez une opération d'agrégation avec un $redactpipeline pour filtrer les documents qui satisfont à la condition donnée.

Le $redactpipeline incorpore la fonctionnalité de $projectet $matchpour implémenter la rédaction au niveau du champ où il renverra tous les documents correspondant à la condition en utilisant$$KEEP et supprime des résultats du pipeline ceux qui ne correspondent pas à l'aide de la $$PRUNEvariable.


L'exécution de l'opération d'agrégation suivante filtre les documents plus efficacement qu'en utilisant $where pour les grandes collections, car elle utilise un seul pipeline et des opérateurs MongoDB natifs, plutôt que des évaluations JavaScript avec $where, ce qui peut ralentir la requête:

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

qui est une version plus simplifiée de l'intégration des deux pipelines $projectet$match :

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

Avec MongoDB 3.4 et plus récent:

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

le dernier ne semble pas fonctionner avec moi. Le champ isGrade1Greater est correctement ajouté et évalué, mais pour une raison quelconque, la requête correspond à toutes les lignes, quelle que soit la valeur de isGrade1Greater. Quelle pourrait être la cause de ce comportement? EDIT: Qu'à cela ne tienne, je ne passais pas un tableau à aggregate () mais plutôt chaque agrégation en tant que paramètre lui-même, j'ai raté cela.
ThatBrianDude

13

Dans le cas où les performances sont plus importantes que la lisibilité et tant que votre condition consiste en des opérations arithmétiques simples, vous pouvez utiliser un pipeline d'agrégation. Tout d'abord, utilisez $ project pour calculer le côté gauche de la condition (prenez tous les champs sur le côté gauche). Ensuite, utilisez $ match pour comparer avec une constante et un filtre. De cette façon, vous évitez l'exécution de javascript. Voici mon test en 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)

Utilisation d'agrégat:

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

1 boucle, meilleur de 1: 192 ms par boucle

En utilisant find et $ where:

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

1 boucle, meilleur de 1: 4,54 s par boucle

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.