MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Gleichen Sie mindestens N Elemente eines Arrays mit einer Liste von Bedingungen ab

Ihre Frage hat zwei Möglichkeiten für mich, aber vielleicht eine Erklärung, um Ihnen den Einstieg zu erleichtern.

Zunächst muss ich Ihnen erklären, dass Sie die Absicht von $elemMatch und es wird in diesem Fall missbraucht.

Die Idee von $elemMatch besteht darin, ein "Abfragedokument" zu erstellen, das tatsächlich auf die Elemente des Arrays angewendet wird. Die Absicht besteht darin, dass Sie "mehrere Bedingungen" für ein Dokument innerhalb des Arrays haben, um es diskret innerhalb des Mitgliedsdokuments und nicht innerhalb des gesamten Arrays des äußeren Dokuments abzugleichen. d.h.:

{
   "data": [
       { "a": 1, "b": 3 },
       { "a": 2, "b": 2 }
   ]
}

Und die folgende Abfrage funktioniert, obwohl kein einzelnes Element in diesem Array übereinstimmt, aber das gesamte Dokument:

db.collection.find({ "data.a": 1, "data.b": 2 })

Aber um zu überprüfen, ob ein tatsächliches Element beide Bedingungen erfüllt, verwenden Sie hier $elemMatch :

db.collection.find({ "data": { "a": 1, "b": 2 } })

Also keine Übereinstimmung in diesem Beispiel, und es wird nur übereinstimmen, wo ein bestimmtes Array-Element beide dieser Elemente hatte.

Jetzt haben wir $elemMatch erklärt, hier ist Ihre vereinfachte Abfrage:

db.collection.find({ "tracks.artist": { "$in": arr } })

Viel einfacher, und es funktioniert, indem es alle Array-Mitglieder mit einem einzigen Feld betrachtet und zurückgibt, wo irgendein Element im Dokument mindestens eines dieser möglichen Ergebnisse enthält.

Aber nicht das, was Sie fragen, also weiter mit Ihrer Frage. Wenn Sie diese letzte Aussage durchlesen, sollten Sie feststellen, dass $in ist eigentlich ein $or Bedingung. Es ist nur eine Kurzform für die Frage „oder“ über dasselbe Element im Dokument.

In Anbetracht dessen ist das Herzstück Ihrer Frage ein "und" Operation, bei der alle "drei" Werte enthalten sind. Angenommen, Sie haben im Test nur „drei“ Elemente gesendet, dann könnten Sie eine Form von $and Dies ist die Kurzform von $all :

db.collection.find({ "tracks.artist": { "$all": arr } })

Das würde Ihnen nur die Dokumente zurückgeben, die das Element innerhalb von Mitgliedern dieses Arrays enthielten, das mit "allen" Elementen übereinstimmt, die in der Testbedingung angegeben sind. Das kann gut sein, was Sie wollen, aber es gibt den Fall, in dem Sie natürlich eine Liste von sagen wir "vier oder mehr" Künstlern zum Testen angeben möchten und nur "drei" oder eine geringere Anzahl davon wollen, in diesem Fall ein $all Operator ist zu knapp.

Aber es gibt einen logischen Weg, dies zu lösen, es erfordert nur etwas mehr Verarbeitung mit Operatoren, die für grundlegende Abfragen nicht verfügbar sind, aber für Aggregationsframework :

var arr = ["A","B","C","D"];     // List for testing

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Test the array conditions
    { "$project": {
        "user": 1,
        "tracks": 1,                         // any fields you want to keep
        "matched": {
            "$gte": [
                 { "$size": {
                     "$setIntersection": [
                         { "$map": {
                             "input": "$tracks",
                             "as": "t",
                             "in": { "$$t.artist" }
                         }},
                         arr
                     ]
                 }},
                 3
             ]
        }
    }},

    // Filter out anything that did not match
    { "$match": { "matched": true } }
])

Die erste Stufe implementiert eine Standardabfrage $match Bedingung, um die Dokumente nur nach denjenigen zu filtern, die "wahrscheinlich" den Bedingungen entsprechen. Der logische Fall hier ist die Verwendung von $in wie zuvor findet es die Dokumente, in denen mindestens eines der in Ihrem "test"-Array vorhandenen Elemente in mindestens einem der Mitgliedsfelder im eigenen Array des Dokuments vorhanden ist.

Die nächste Klausel sollten Sie idealerweise in Code einbauen, da sie sich auf die "Länge" des Arrays bezieht. Die Idee hier ist, dass Sie mindestens "drei" Übereinstimmungen wünschen, dann muss das Array, das Sie im Dokument testen, mindestens "drei" Elemente haben, um dies zu erfüllen, also keinen Sinn darin, Dokumente mit "zwei" oder weniger Array-Elementen abzurufen da sie niemals "drei" entsprechen können.

Da alle MongoDB-Abfragen im Wesentlichen nur eine Darstellung einer Datenstruktur sind, ist diese sehr einfach zu erstellen. d.h. für JavaScript:

var matchCount = 3;    // how many matches we want

var match1 = { "$match": { "tracks.artist": { "$in": arr } } };

match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };

Die Logik dort ist, dass die „Punktnotation“ mit $exists testet auf das Vorhandensein eines Elements am angegebenen Index ( n-1 ), und es muss vorhanden sein, damit das Array mindestens diese Länge hat.

Der Rest der Eingrenzung verwendet idealerweise den $setIntersection -Methode, um die übereinstimmenden Elemente zwischen dem tatsächlichen Array und dem getesteten Array zurückzugeben. Da das Array im Dokument nicht mit der Struktur für das "Test-Array" übereinstimmt, muss es über den $map Operation, die so eingestellt ist, dass sie nur das "Künstler"-Feld von jedem Array-Element zurückgibt.

Wenn die "Schnittmenge" dieser beiden Arrays hergestellt wird, wird sie schließlich auf $size dieser resultierenden Liste gemeinsamer Elemente, wobei der Test angewendet wird, um festzustellen, dass "mindestens drei" dieser Elemente gemeinsam gefunden wurden.

Schließlich "filtern" Sie alles, was nicht wahr ist, einfach mit einem $match Zustand.

Idealerweise verwenden Sie MongoDB 2.6 oder höher, um diese Operatoren verfügbar zu haben. Für die früheren Versionen von 2.2.x und 2.4.x ist es immer noch möglich, aber nur etwas mehr Arbeit und Verarbeitungsaufwand:

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Unwind the document array
    { "$unwind": "$tracks" },

    // Filter the content
    { "$match": { "tracks.artist": { "$in": arr } }},

    // Group for distinct values
    { "$group": {
        "_id": { 
           "_id": "$_id",
           "artist": "$tracks.artist"
        }
    }},

    // Make arrays with length
    { "$group": {
        "_id": "$_id._id",
        "artist": { "$push": "$_id.artist" },
        "length": { "$sum": 1 }
    }},

    // Filter out the sizes
    { "$match": { "length": { "$gte": 3 } }}
])