MongoDB: Wie kann man herausfinden, ob ein Array-Feld ein Element enthält?

75

Ich habe zwei Sammlungen. Die erste Sammlung enthält Studenten:

{ "_id" : ObjectId("51780f796ec4051a536015cf"), "name" : "John" }
{ "_id" : ObjectId("51780f796ec4051a536015d0"), "name" : "Sam" }
{ "_id" : ObjectId("51780f796ec4051a536015d1"), "name" : "Chris" }
{ "_id" : ObjectId("51780f796ec4051a536015d2"), "name" : "Joe" }

Die zweite Sammlung enthält Kurse:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc5"),
        "name" : "Literature",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0")
        ]
}

Jedes Kursdokument enthält ein studentsArray mit einer Liste der für den Kurs registrierten Studenten. Wenn ein Student einen Kurs auf einer Webseite anzeigt, muss er prüfen, ob er sich bereits für den Kurs angemeldet hat oder nicht. Wenn die coursesSammlung im Namen des Schülers abgefragt wird, müssen wir dazu herausfinden, ob das studentsArray bereits die ObjectId des Schülers enthält. Gibt es eine Möglichkeit, in der Projektion einer Suchabfrage anzugeben, dass ein Schüler nur dann ObjectIdaus dem studentsArray abgerufen werden soll, wenn er vorhanden ist?

Ich habe versucht zu sehen, ob ich den Operator $ elemMatch verwenden kann, aber er ist auf eine Reihe von Unterdokumenten ausgerichtet. Ich verstehe, dass ich ein Aggregationsframework verwenden könnte, aber es scheint, dass es in diesem Fall zu viel des Guten wäre. Das Aggregationsframework wäre wahrscheinlich nicht so schnell wie eine einzelne Suchabfrage. Gibt es eine Möglichkeit, die Kurssammlung abzufragen, damit das zurückgegebene Dokument eine ähnliche Form hat?

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
        ]
}
Shargors
quelle

Antworten:

71

[ Bearbeitung basierend darauf ist jetzt in neueren Versionen möglich]

[Aktualisierte Antwort] Sie können den folgenden Weg abfragen, um den Namen der Klasse und die Schüler-ID nur dann zurückzugewinnen, wenn sie bereits eingeschrieben sind.

db.student.find({},
 {_id:0, name:1, students:{$elemMatch:{$eq:ObjectId("51780f796ec4051a536015cf")}}})

und Sie erhalten zurück, was Sie erwartet haben:

{ "name" : "CS 101", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }
{ "name" : "Literature" }
{ "name" : "Physics", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }

[Originalantwort] Es ist derzeit nicht möglich, das zu tun, was Sie möchten. Dies ist bedauerlich, da Sie dies tun könnten, wenn der Schüler als Objekt im Array gespeichert wäre. Tatsächlich bin ich ein wenig überrascht, dass Sie nur ObjectId () verwenden, da Sie immer die Schüler nachschlagen müssen, wenn Sie eine Liste der in einem bestimmten Kurs eingeschriebenen Schüler anzeigen möchten (zuerst die Liste der IDs nachschlagen und dann nachsehen) up Namen in der Studentensammlung - zwei Abfragen statt einer!)

Wenn Sie (als Beispiel) eine ID und einen Namen wie folgt im Kursarray gespeichert haben:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                {id: ObjectId("51780f796ec4051a536015cf"), name: "John"},
                {id: ObjectId("51780f796ec4051a536015d0"), name: "Sam"}
        ]
}

Ihre Anfrage wäre dann einfach:

db.course.find( { }, 
                { students : 
                    { $elemMatch : 
                       { id : ObjectId("51780f796ec4051a536015d0"), 
                         name : "Sam" 
                       } 
                    } 
                } 
);

Wenn dieser Student nur in CS 101 eingeschrieben wäre, würden Sie zurückkommen:

{ "name" : "Literature" }
{ "name" : "Physics" }
{
    "name" : "CS 101",
    "students" : [
        {
            "id" : ObjectId("51780f796ec4051a536015cf"),
            "name" : "John"
        }
    ]
}
Asya Kamsky
quelle
Vielen Dank, dass Sie meine Überlegungen bestätigt haben, dass einfache Arrays sowie Arrays von Unterdokumenten nicht abgefragt werden können. Ich denke, ich muss mein Schema neu bewerten. Wissen Sie, ob 10Gen plant, dieses Problem zu beheben?
Shargors
Ich konnte auf jira.mongodb.org kein Jira-Ticket dafür finden und es wird nichts für eine Veröffentlichung ohne Ticket geplant, daher würde ich definitiv nicht in Kürze sagen.
Asya Kamsky
Was ist mit der Verwendung von $ all, wie in stackoverflow.com/questions/8145523/…
Xerri
$ all ist ein Operator zum Abfragen - das Abfragen ist kein Problem, das Problem kehrt zurück, daher werden die Projektionsoperatoren diskutiert.
Asya Kamsky
Das Problem beim Speichern des Namens und der Objekt-ID in der courseSammlung besteht darin, dass Sie den Schülernamen an mehreren Stellen gespeichert haben, was möglicherweise zu einer Synchronisierung führen kann. In diesem Fall würde Ihre Abfrage unvollständige Daten zurückgeben. Wenn die Serverlast und die Hardware nicht sehr dünn sind, ist die zusätzliche Abfrage zum Abrufen des Namens möglicherweise vorzuziehen. Sie können weiterhin Schüler ObjectIdals Objekt speichern, um die Abfrage des OP zuzulassen:{"id":...}
drevicko
21

Es scheint, als würde der $inBetreiber Ihren Zwecken gut dienen.

Sie könnten so etwas tun (Pseudo-Abfrage):

if (db.courses.find({"students" : {"$in" : [studentId]}, "course" : courseId }).count() > 0) {
  // student is enrolled in class
}

Alternativ können Sie die "course" : courseIdKlausel entfernen und eine Reihe aller Klassen zurückerhalten, in denen der Schüler eingeschrieben ist.

xbonez
quelle
Das Problem bei diesem Ansatz ist, dass ein Dokument aus der Kurssammlung nur zurückgegeben wird, wenn sich der Student für den Kurs registriert hat. Wenn sich der Schüler nicht registriert hat, ist eine zweite Abfrage erforderlich. Das ist nicht effizient.
Shargors
Die zweite Abfrage wäre, die Objekt-ID des Schülers abzurufen. Aber wäre das nicht schon bekannt (schließlich verwenden Sie es, um die erste Abfrage durchzuführen).
Xbonez
Das Endziel besteht darin, das Kursdokument abzurufen und eine Flagge zu haben, die angibt, ob sich der Student dafür registriert hat. Die von Ihnen angegebene Abfrage ruft das Kursdokument nur ab, wenn sich der Student dafür registriert hat. Wenn sich der Student nicht für den Kurs angemeldet hat, ist eine weitere Abfrage erforderlich, um das Kursdokument abzurufen.
Shargors
1
Ich glaube nicht, dass mit dem Datenmodell etwas nicht stimmt. Ich vermute, es gibt keinen wirklichen Weg, um das zu erreichen, was Sie wollen, ohne zwei Fragen, weil Sie Ihrer Datenbank am Ende des Tages zwei Fragen stellen: Geben Sie mir das Kursdokument UND ist der Student im Kurs eingeschrieben. Selbst in einer traditionellen relationalen Datenbank würden Sie das gleiche Problem haben.
Xbonez
1
Späte Antwort (sorry!), Aber nein, das war nie das Ziel von Mongo. Mongo soll nicht relationale, schemenlose Datenobjekte speichern. AFAIK macht keinen Versuch, die Anzahl der benötigten Abfragen zu reduzieren.
Xbonez
0

Ich versuche es zu erklären, indem ich eine Problemstellung und eine Lösung dafür vorlege. Ich hoffe es wird helfen

Problemstellung:

Finden Sie alle veröffentlichten Produkte, deren Name wie ABC-Produkt oder PQR-Produkt lautet und deren Preis unter 15 / - liegen sollte.

Lösung:

Nachfolgend sind die Bedingungen aufgeführt, die berücksichtigt werden müssen

  1. Der Produktpreis sollte unter 15 liegen
  2. Der Produktname sollte entweder ABC-Produkt oder PQR-Produkt sein
  3. Das Produkt sollte sich im veröffentlichten Zustand befinden.

Unten finden Sie die Anweisung, die über dem oben genannten Kriterium zum Erstellen von Abfrage- und Abrufdaten gilt.

$elements = $collection->find(
             Array(
                [price] => Array( [$lt] => 15 ),
                [$or] => Array(
                            [0]=>Array(
                                    [product_name]=>Array(
                                       [$in]=>Array(
                                            [0] => ABC Product,
                                            [1]=> PQR Product
                                            )
                                        )
                                    )
                                ),
                [state]=>Published
                )
            );
Anup
quelle