web-dev-qa-db-fra.com

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

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

Select * from T Where Grade1 > Grade2
91
Diego Cheng

Vous pouvez utiliser un $ where. Sachez simplement que ce sera assez lent (doit exécuter du code Javascript sur chaque enregistrement), donc combinez-le avec les 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 $expr comme décrit dans réponse récente

111
Ian

Si votre requête ne contient que $where == opérateur, vous pouvez ne transmettre que l'expression JavaScript:

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

Pour de meilleures performances, exécutez une opération d'agrégat qui a $redact pipeline pour filtrer les documents qui satisfont à la condition donnée.

Le $redact Le pipeline intègre les fonctionnalités de $project et $match pour implémenter la rédaction au niveau du champ où tous les documents correspondant à la condition seront renvoyés à l'aide de $$KEEP et supprime des résultats de pipeline ceux qui ne correspondent pas à l'aide de $$Prune variable.


L'exécution de l'opération d'agrégation suivante permet de filtrer les documents plus efficacement que d'utiliser $where pour les grandes collections car il utilise un seul pipeline et des opérateurs natifs MongoDB, 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’incorporation des deux pipelines $project et $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 } }
])
35
chridam

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

Comparez query operators vs 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"]}}})
32
user2683814

Si les performances sont plus importantes que la lisibilité et tant que votre condition est constituée d'opérations arithmétiques simples, vous pouvez utiliser le pipeline d'agrégation. Commencez par utiliser $ project pour calculer le côté gauche de la condition (placez tous les champs sur le côté gauche). Puis utilisez $ match pour comparer avec une constante et un filtre. De cette façon, vous évitez l'exécution de javascript. Ci-dessous, 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, le meilleur de 1: 192 ms par boucle

Utiliser find et $ where:

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

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

11
Sina