Abfrage für boolesches Feld als "nicht wahr" (z. B. entweder falsch oder nicht vorhanden)

75

Ich bin sicher, ich vermisse etwas sehr Grundlegendes in MongoDB-Abfragen, kann diese einfache Bedingung nicht bekommen.

Betrachten Sie diese Sammlung

> db.tests.find()
{ "_id" : ObjectId("..."), "name" : "Test1" , "deleted" : true}
{ "_id" : ObjectId("..."), "name" : "Test2" , "deleted" : false}
{ "_id" : ObjectId("..."), "name" : "Test3" }

Ich möchte einfach alle Elemente abfragen, die "nicht gelöscht" sind.

Ich weiß, wie ich das Element finde, bei dem das Flag "Gelöscht" auf "Wahr" gesetzt ist:

> db.tests.find({deleted:true})
{ "_id" : ObjectId("..."), "name" : "Test1" , "deleted" : true}

Aber wie finde ich alle Elemente, die NICHT sind "deleted"(z. B. die obige Abfrage negieren, oder mit anderen Worten, alle Elemente, die entweder kein "deleted"Feld oder einen Wert haben?false

Was ich durch Raten versucht habe (bitte nicht lachen ...)

> db.tests.find({$not : {deleted: true}})

(gibt keine Ergebnisse zurück)

> db.tests.find({$not : {$eq:{deleted:true}}})

Fehler: {"$ err": "ungültiger Operator: $ eq", "Code": 10068}

> db.tests.find({deleted:{$not: true}})

Fehler: {"$ err": "ungültige Verwendung von $ not", "Code": 13041}

> db.tests.find({deleted:{$not: {$eq:true}}})

Fehler: {"$ err": "ungültige Verwendung von $ not", "Code": 13034}

Was vermisse ich?

Eran Medan
quelle

Antworten:

145
db.tests.find({deleted: {$ne: true}})

Wo $nesteht für "ungleich". ( Dokumentation zu Mongodb-Betreibern )

Sergio Tulentsev
quelle
2
Laut den Dokumenten scheint die Verwendung von $ ne keinen Index zu verwenden - gibt es eine andere Lösung, die einen Index nutzen könnte? docs.mongodb.org/manual/faq/indexes/…
joelsand
2
Nur um es für andere Leser erneut zu wiederholen - diese Abfrage verwendet den Index nicht . Also bitte nicht in der Produktion verwenden.
UpTheCreek
Was sollte stattdessen in der Produktion verwendet werden?
JJJ
9
@Juhana Ich habe dies gerade mit MongoDB 2.6.6 getestet und es wird jetzt ein Index verwendet, der für erstellt wurde {deleted: 1}.
JohnnyHK
2
Indizierte unter 3.6.8 mit zusammengesetzten Indizes nicht korrekt, $in: [false,null]schien aber zu funktionieren.
Jimrandomh
30

Der Vollständigkeit halber können Sie dies auch mit $infolgenden Methoden tun :

db.test.find({deleted: {$in: [null, false]}})

Durch die Aufnahme nullin das Array werden die Dokumente abgerufen, in denen das deletedFeld fehlt. Diese Abfrage kann einen Index für {deleted: 1}die aktuelle Version 2.6.6 MongoDB verwenden.

JohnnyHK
quelle
6

JohnnyHK hat die beste Antwort. Der $inSelektor ist die kürzeste und sauberste IMO.

Dies wird auf genau "falsch" oder "nicht vorhanden" getestet. Und kann indiziert werden.

db.tests.find({$or:[{deleted:false},{deleted:{$exists:false}}]})

Ein Beispiel mit der Verwendung eines Index.

((function(){
    print("creating collection 'testx' and inserting 50 trues, 50 falses, 50 non-existents");
    db.testx.drop();
    db.testx.ensureIndex({deleted:1});
    for (var i=0;i<50;i++){
        db.testx.insert({i:i,deleted:false});
    };
    for (var i=0;i<50;i++){
        db.testx.insert({i:i,deleted:true});
    };
    for (var i=0;i<50;i++){
        db.testx.insert({i:i});
    };
    var res0 = db.testx.find().explain();
    var res1 = db.testx.find({deleted:false}).explain();
    var res2 = db.testx.find({deleted:true}).explain();
    var res3 = db.testx.find({deleted:{$exists:false}}).explain();
    var res4 = db.testx.find({$or:[{deleted:false},{deleted:{$exists:false}}]}).explain();
    var res5 = db.testx.find({$or:[{deleted:true},{deleted:{$exists:false}}]}).explain();
    var res6 = db.testx.find({deleted:{$in:[false,null]}}).explain();
    print("res0: all objects                      ("+res0["n"]+" found, "+res0["nscannedObjects"]+" scanned)");
    print("res1: deleted is false                 ("+res1["n"]+" found, "+res1["nscannedObjects"]+" scanned)");
    print("res2: deleted is true                  ("+res2["n"]+" found, "+res2["nscannedObjects"]+" scanned)");
    print("res3: deleted is non-existent          ("+res3["n"]+" found, "+res3["nscannedObjects"]+" scanned)");
    print("res4: deleted is false or non-existent ("+res4["n"]+" found, "+res4["nscannedObjects"]+" scanned)");
    print("res5: deleted is true or non-existent  ("+res5["n"]+" found, "+res5["nscannedObjects"]+" scanned)");
    print("res6: deleted is in [false,null]       ("+res5["n"]+" found, "+res5["nscannedObjects"]+" scanned)");
})())

Dies sollte gedruckt werden

creating collection 'testx' and inserting 50 trues, 50 falses, 50 non-existents
res0: all objects                      (150 found, 150 scanned)
res1: deleted is false                 (50 found, 50 scanned)
res2: deleted is true                  (50 found, 50 scanned)
res3: deleted is non-existent          (50 found, 50 scanned)
res4: deleted is false or non-existent (100 found, 100 scanned)
res5: deleted is true or non-existent  (100 found, 100 scanned)
res6: deleted is in [false,null]       (100 found, 100 scanned)
tidwall
quelle
Wenn ich diese Abfrage erkläre, wird angezeigt, dass der Index nur für den Teil 'gelöscht: falsch' der Abfrage verwendet wird. Das 'gelöscht: {$ existiert: falsch}' scheint den Index nicht zu verwenden.
Emilebaizel
@emilebaizel $ existiert sollte für die Indizierung verfügbar sein. Ältere Versionen von Mongo <2.5.5 verfügen möglicherweise nicht über diese Funktion. jira.mongodb.org/browse/SERVER-10608
Tidwall
Aha! das ist es wahrscheinlich. wir sind auf 2.4.
Emilebaizel
1

Für den Fall, dass jemand dies in einer Aggregationspipeline anstelle von benötigt find, hat dies für mich funktioniert

db.getCollection('tests').aggregate([ 
  // ...previous operations...
  { $addFields: { "deleted_conclusion": { $cond: {
        if:{ $ne: [ "$deleted", false ]}, then: { $cond: [ "$deleted", ":TRUE", ":FALSY"]}, else: ":FALSE"
  }}}}
])

Nachdem Sie das zusätzliche Feld hinzugefügt haben, können Sie mit den Pipeline-Phasen fortfahren und die Informationen erhalten, die Sie verpassen

JohnPan
quelle
0

Für den Fall, dass Sie nach einer Mongoid-Syntax suchen (ich verwende diese in einer Rails-App), habe ich Folgendes für die Benutzer eines Unternehmens entwickelt:

2.3.1 :042 > accepted_consent = org.users.active.where(:accepted_terms_and_conditions => true).count
 => 553 
2.3.1 :043 > not_accepted_yet = org.users.active.where(:accepted_terms_and_conditions.ne => true).count
 => 6331 
2.3.1 :044 > 6331+553
 => 6884 
2.3.1 :045 > org.users.active.count
 => 6884 
Jon Kern
quelle