Suchen nach dem Wert eines Feldes in MongoDB, ohne es explizit zu benennen

70

Ich habe die MongoDB-Dokumentation durchgesehen und diese Frage gegoogelt, konnte aber keine passende Antwort finden. Also, hier ist was ich suche. Angenommen, ich habe eine Sammlung mit Elementen wie diesen:

{
   "foo" : "bar",
   "test" : "test",
   "key" : "value",
}

Was ich erreichen möchte, ist ein Element zu finden, indem ich in allen (vielleicht bis auf endlich viele ;-)) Feldern suche. Mit anderen Worten: Bei einer Abfrage weiß ich NICHT, in welchem ​​Feld die Abfrage gefunden werden soll.

In meinem Denken so etwas

db.things.find({_ANY_ : "bar"}) 

würde mir das Beispielelement geben.

Danke für deine Hilfe.

Max L.
quelle
Was Sie also brauchen, ist so etwas wie eine Volltextsuche, die in mongodb nicht nativ implementiert ist. Siehe auch: mongodb.org/display/DOCS/Full+Text+Search+in+Mongo
asaaki
Ich denke nicht, dass dies eine Textsuchfrage ist. Es ist eine Abfrage nach Wert. Beide teilen die Tatsache, dass es im Mongo keine native Unterstützung dafür gibt;)
Remon van Vliet
1
@asaaki: In der Zwischenzeit hat Mongo die Volltextsuche implementiert . Lassen Sie uns unsere Kommentare löschen.
Dan Dascalescu

Antworten:

42

Um eine Textsuche für alle Felder durchzuführen, müssen Sie zuerst einen Textindex für alle Felder erstellen.

Wie in der Mongodb-Dokumentation angegeben: "Um die Textsuche in allen Feldern mit Zeichenfolgeninhalt zu ermöglichen, verwenden Sie den Platzhalterbezeichner ($ **), um alle Felder zu indizieren, die Zeichenfolgeninhalte enthalten."

Wenn Sie in der Mongo-Shell arbeiten (die Sie über die Befehlszeile ausführen, indem Sie 'mongo' aufrufen), können Sie dies mit diesem Befehl tun, wobei 'collection' der Name der Sammlung in der Datenbank ist, die Sie verwenden möchten.

db.collection.createIndex({ "$**": "text" },{ name: "TextIndex" })

Das zweite Objekt {name:"TextIndex"}ist optional. Sie müssen dem Index keinen Namen geben, da es nur einen einzigen Textindex pro Sammlung geben kann (gleichzeitig ... können Sie Indizes löschen und neue erstellen falls Sie es wollen).

Nachdem Sie einen Textindex für alle Felder erstellt haben, können Sie eine einfache Textsuche mit dem folgenden Abfrageobjekt durchführen: { $text : { $search: <your string> } }

Wenn Sie also eine Javascript-Funktion schreiben, können Sie Folgendes tun:

var cursor = db.collection(<collection_name>).find({ $text: { $search: <your string> } });

Weitere Informationen zu den verschiedenen Möglichkeiten zur Steuerung der Suche finden Sie in der Mongodb-Dokumentation zur Textsuche hier

Dave Adelson
quelle
1
Wenn die Zeichenfolge, die ein Wort umgibt, entkommen ist, verwandelt sie die Suche in eine 'und' Suche anstelle einer 'oder' Suche. {$ Text: {$ search: "jim" "Gewohnheit" }} findet alle Datensätze mit 'Jim' und 'Habitus' im Textindex.
Keith John Hutchison
1
@ Dave-Adelson Wie benutzt man Regex? Ich habe versucht, Regex in zu verwenden, aber es gibt die Ausnahme "errmsg": "\" $ search \ "hatte den falschen Typ. Erwartete Zeichenfolge, RegEx gefunden", Abfrage: find ({$ text: {$ search: / version /}} )
Vivek Singh
31

Diese Antwort auf eine ähnliche Frage hat Ihre Lösung, die ich hier der Vollständigkeit halber wiederholen werde. Sie können den $whereOperator verwenden, um beliebiges JavaScript auf den MongoDB-Servern auszuführen, mit der Einschränkung, dass dies viel langsamer ist als bei fast jeder anderen Art von Abfrage. Für Ihr Beispiel wäre es:

db.things.find({$where: function() {
    for (var key in this) {
        if (this[key] === "bar") {
            return true;
        }
    }
    return false;
}});
Tom Panning
quelle
return false;sollte eine Klammer weiter unten sein
Michał Sadowski
"In Version 3.6 geändert: Der Operator $ expr ermöglicht die Verwendung von Aggregationsausdrücken innerhalb der Abfragesprache. $ Expr ist schneller als $ where, da es kein JavaScript ausführt und nach Möglichkeit bevorzugt werden sollte." - Quelle
CodeFinity
26

Dies ist nicht möglich, ohne Dokumente auf App-Seite oder durch serverseitige Codeausführung einzeln zu überprüfen. Ändern Sie Ihr Schema in:

{params:[{field:"foo", value:"bar"}, {field:"test", value:"test"}, {field:"key", value:"value"}]}

Dies hat natürlich einige Nachteile (Leistung und meistens verschmutztes Schema), lässt aber zu, was Sie brauchen:

db.things.find({'params.value':"bar"})
Remon van Vliet
quelle
Wie kann ich mit diesem Schema nach einem bestimmten Feld sortieren? Ich meine so etwas wie .sort ({foo: 1}) im OP.
runTarm
1
Das tust du nicht. find ({'params.field': "foo"}). sort ({'params.value': 1}) ist aufgrund der Funktionsweise der Sortierung in Arrays nicht gleichwertig.
Remon van Vliet
Wie können wir eine Repository-Methode für die Spring Data Mongo-Abfrage schreiben? Bitte, bitte, bitte hilf mir. Danke, Neha
Eine Volltextsuche ist einfacher.
Keith John Hutchison
Dies scheint der Antwort von Dave Adelson zu widersprechen. Liegt das daran, dass diese Antwort veraltet ist? Oder als Sie "serverseitige Codeausführung" sagten, haben Sie sich auf die Erstellung eines Index bezogen? Oder schlagen Sie vor, dass die Option eingeschränkt ist, weil nur Text und keine anderen Typen durchsucht werden?
Drew Nutter
20

Leider geht keine der vorherigen Antworten auf die Tatsache ein, dass Mongo verschachtelte Werte in Arrays oder verschachtelten Objekten enthalten kann.

DAS IST DIE RICHTIGE FRAGE:

{$where: function() {
    var deepIterate = function  (obj, value) {
        for (var field in obj) {
            if (obj[field] == value){
                return true;
            }
            var found = false;
            if ( typeof obj[field] === 'object') {
                found = deepIterate(obj[field], value)
                if (found) { return true; }
            }
        }
        return false;
    };
    return deepIterate(this, "573c79aef4ef4b9a9523028f")
}}

Da der Aufruf von typeof für ein Array oder ein verschachteltes Objekt 'object' zurückgibt, bedeutet dies, dass die Abfrage alle verschachtelten Elemente durchläuft und dann alle durchläuft, bis der Schlüssel mit dem Wert gefunden wird.

Sie können frühere Antworten mit einem verschachtelten Wert überprüfen, und die Ergebnisse sind bei weitem nicht erwünscht.

Das Stringisieren des gesamten Objekts ist weitaus weniger performant, da es alle Speichersektoren nacheinander durchlaufen muss, um sie abzugleichen. Und erstellt eine Kopie des Objekts als Zeichenfolge im RAM-Speicher (sowohl ineffizient, da die Abfrage mehr RAM verwendet, als auch langsam, da der Funktionskontext bereits ein geladenes Objekt enthält).

twboc
quelle
3
wir können if (obj [field] == value) wie - if (typeof obj [field] == 'string' && obj [field] .contains (/ value /)) umschreiben. Es gibt genauere Suchergebnisse
Lokesh Boran
@LokeshBoran es gibt mir diesen Fehler TypeError: Ungültiger Typ: zuerst kann kein regulärer Ausdruck sein: \ ndeepIterate @: 4: 49 \ ndeepIterate @: 9: 25 \ n @: 15: 12 \ n
prakharjain
@prakharjain Könnten Sie Ihren Code in einem Einfügebehälter veröffentlichen? Also könnten wir es uns ansehen?
Twboc
4

Sie können dies mit einer rekursiven Funktion tun:

var recursiveSearch = function(query) {
    db.test_insert.find().forEach(function(items) {
        var i = 0;
        var recursiveFunc = function(itemsArray, itemKey) {
            var itemValue = itemsArray[itemKey];
            if(itemValue === query) { 
                printjson(items);
            }       

            if(typeof itemValue === "object") {
                Object.keys(itemValue).forEach(function(itemValueKey) {
                    recursiveFunc(itemValue, itemValueKey);
                });
            }
        };
        Object.keys(items).forEach(function(item){
            recursiveFunc(items, item);
        });
    });
};

recursiveSearch('your string');
RoXuS
quelle
4

Die Verwendung von $ where entspricht der Durchführung eines vollständigen Tabellenscans und kann die Indizes nicht verwenden. Ich konnte es auch nicht zum Laufen bringen, fand es jedoch funktionierend (es entspricht auch einem vollständigen Tabellenscan):

db.collection.find().forEach(function(doc){
for (var key in doc) {
    if ( /needle/.test(doc[key]) )
        printjson(doc);
    }
});

wo /needle/ist ein regulärer Ausdruck im Wert von zu findendoc[key]

Paddel
quelle
2

Um eine Textsuche durchzuführen, müssen Sie Textindizes für Ihre Sammlung erstellen. Weitere Informationen finden Sie in der Mongo-Dokumentation: Indiziert Text

Salim Hamidi
quelle
-11
{
   "foo" : "bar",
   "test" : "test",
   "key" : "value",
}

db.collection_name.find({'foo':"bar"})
rahul
quelle
Dies ist nicht das, was das OP verlangt hat. Die Frage war, wie man Elemente findet, ohne den Feldnamen anzugeben.
user2223059