MongoDb-Abfragebedingung beim Vergleichen von 2 Feldern


108

Ich habe eine Sammlung Tmit 2 Feldern: Grade1und Grade2, und ich möchte diejenigen mit Bedingung auswählen Grade1 > Grade2, wie kann ich eine Abfrage wie in MySQL erhalten?

Select * from T Where Grade1 > Grade2

Antworten:


120

Sie können ein $ where verwenden. Beachten Sie jedoch, dass es ziemlich langsam sein wird (Javascript-Code muss für jeden Datensatz ausgeführt werden). Kombinieren Sie es daher mit indizierten Abfragen, wenn Sie können.

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

oder kompakter:

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

UPD für mongodb v.3.6 +

Sie können $exprwie in der letzten Antwort beschrieben verwenden


Hoppla, ich verstehe, nur gemeinsam verwendetes Javascript oder andere Shell-Skripte, danke euch beiden!
Diego Cheng

16
Sie können dies auch etwas kompakter tun ...> db.T.find ({$ where: "this.Grade1> this.Grade2"});
Justin Jenkins

Wie könnte ich $where: function() { return this.Grade1 - this.Grade2 > variable }?
Luis González

Als ich es versuchte, gab db.T.find({$where: function() {return this.startDate == ISODate("2017-01-20T10:55:08.000Z");}});es nichts zurück, sogar eines der Dokumente in der Sammlung ist ISODate("2017-01-20T10:55:08.000Z"). Aber <=und >=scheinen Arbeit. irgendeine Idee?
Kateyes

@cateyes Vielleicht etwas spät ... aber reines Javascript gibt immer false zurück, wenn ein == Vergleich zwischen zwei Daten durchgeführt wird. Mit Mongo können Sie jedoch nach genauen Übereinstimmungen zwischen Daten in Abfragen suchen. Eine Problemumgehung ist die Verwendung von .getTime () zur Konvertierung in Millisekunden oder was auch immer:this.startDate.getTime() == ISODate("2017-01-20T10:55:08.000Z").getTime()
leinaD_natipaC

53

Sie können $ expr (3.6 Mongo-Versionsoperator) verwenden, um Aggregationsfunktionen in regulären Abfragen zu verwenden.

Vergleichen query operatorsvs aggregation comparison operators.

Regelmäßige Abfrage:

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

Aggregationsabfrage:

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

39

Wenn Ihre Abfrage nur aus dem $whereOperator besteht, können Sie nur den JavaScript-Ausdruck übergeben:

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

Führen Sie für eine höhere Leistung eine Aggregatoperation mit einer $redactPipeline aus, um die Dokumente zu filtern, die die angegebene Bedingung erfüllen.

Die $redactPipeline enthält die Funktionalität von $projectund $matchzum Implementieren von Redaktionen auf Feldebene, bei denen alle Dokumente zurückgegeben werden, die der Bedingung entsprechen, $$KEEPund diejenigen aus der Pipeline entfernt werden, die nicht mit der $$PRUNEVariablen übereinstimmen.


Durch Ausführen der folgenden Aggregatoperation werden die Dokumente effizienter gefiltert als $wherefür große Sammlungen, da hierfür eine einzelne Pipeline und native MongoDB-Operatoren anstelle von JavaScript-Auswertungen verwendet werden $where, wodurch die Abfrage verlangsamt werden kann:

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

Dies ist eine vereinfachte Version der Integration der beiden Pipelines $projectund $match:

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

Mit MongoDB 3.4 und neuer:

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

Der letzte scheint nicht mit mir zu arbeiten. Das Feld isGrade1Greater wird ordnungsgemäß hinzugefügt und ausgewertet. Aus irgendeinem Grund stimmt die Abfrage jedoch mit allen Zeilen überein, unabhängig vom Wert von isGrade1Greater. Was könnte die Ursache für dieses Verhalten sein? EDIT: Egal, ich habe kein Array an aggregat () übergeben, sondern jede Aggregation als Parameter selbst hat das verpasst.
ThatBrianDude

13

Wenn die Leistung wichtiger ist als die Lesbarkeit und Ihre Bedingung aus einfachen arithmetischen Operationen besteht, können Sie die Aggregationspipeline verwenden. Verwenden Sie zuerst $ project, um die linke Seite der Bedingung zu berechnen (nehmen Sie alle Felder auf die linke Seite). Verwenden Sie dann $ match, um mit einer Konstante und einem Filter zu vergleichen. Auf diese Weise vermeiden Sie die Ausführung von Javascript. Unten ist mein 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)

Aggregat verwenden:

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

1 Schleife, am besten 1: 192 ms pro Schleife

Mit find und $ where:

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

1 Schleife, am besten 1: 4,54 s pro Schleife

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.