MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Dopasuj co najmniej N elementów tablicy do listy warunków

Twoje pytanie ma dla mnie dwie możliwości, ale być może jakieś wyjaśnienie na początek.

Przede wszystkim muszę ci wyjaśnić, że źle rozumiesz intencje $elemMatch i jest niewłaściwie używany w tym przypadku.

Pomysł $elemMatch jest stworzenie „dokumentu zapytania”, który jest faktycznie stosowany do elementów tablicy. Intencją jest to, że masz „wiele warunków” na dokumencie w tablicy, aby dyskretnie dopasować go do dokumentu członkowskiego, a nie do całej tablicy dokumentu zewnętrznego. czyli:

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

Poniższe zapytanie będzie działać, nawet jeśli żaden pojedynczy element w tej tablicy nie pasuje, ale cały dokument tak:

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

Ale aby sprawdzić, czy rzeczywisty element spełnia oba te warunki, użyj $elemMatch :

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

Więc nie ma dopasowania w tej próbce i będzie pasować tylko tam, gdzie określony element tablicy miał oba te elementy.

Teraz mamy $elemMatch wyjaśniono, oto Twoje uproszczone zapytanie:

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

O wiele prostszy i działa, patrząc na wszystkie elementy tablicy według jednego pola i zwracając, gdzie dowolny element w dokumencie zawiera co najmniej jeden z tych możliwych wyników.

Ale nie o to, o co pytasz, więc kontynuuj swoje pytanie. Jeśli przeczytasz to ostatnie stwierdzenie, powinieneś zdać sobie sprawę, że $w jest w rzeczywistości $or stan. To tylko skrócona forma pytania „lub” nad tym samym elementem w dokumencie.

Mając to na uwadze, sednem tego, o co prosisz, jest „i” operacja, w której zawarte są wszystkie „trzy” wartości. Zakładając, że wysyłałeś tylko „trzy” elementy w teście, możesz użyć formy $i który jest w skróconej formie $all :

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

Spowoduje to zwrócenie tylko dokumentów, które zawierają element w elementach tej tablicy pasujący do „wszystkich” elementów określonych w warunku testowym. To może być to, czego chcesz, ale jest taki przypadek, w którym oczywiście chcesz określić listę, powiedzmy, „czterech lub więcej” artystów do przetestowania i chcesz tylko „trzech” lub mniej z nich, w takim przypadku $all operator jest zbyt lapidarny.

Istnieje jednak logiczny sposób na rozwiązanie tego problemu, wymaga to po prostu trochę więcej przetwarzania z operatorami niedostępnymi dla podstawowych zapytań, ale które są dostępne dla struktura agregacji :

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 } }
])

Pierwszy etap implementuje standardowe zapytanie $match warunek, aby przefiltrować dokumenty tylko do tych, które „z dużym prawdopodobieństwem” spełnią warunki. Logicznym przypadkiem jest tutaj użycie $in tak jak poprzednio, znajdzie te dokumenty, w których co najmniej jeden z elementów obecnych w tablicy "test" jest obecny w co najmniej jednym z pól członkowskich w tablicy dokumentów.

Następna klauzula to coś, co najlepiej byłoby zbudować w kodzie, ponieważ odnosi się ona do „długości” tablicy. Pomysł polega na tym, że chcesz, aby pasowały co najmniej "trzy", a tablica, którą testujesz w dokumencie, musi zawierać co najmniej "trzy" elementy, aby to spełnić, więc nie ma sensu pobierać dokumentów z "dwoma" lub mniej elementami tablicy ponieważ nigdy nie mogą dopasować „trzech”.

Ponieważ wszystkie zapytania MongoDB są w zasadzie tylko reprezentacją struktury danych, bardzo łatwo jest ją zbudować. tj. dla JavaScript:

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

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

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

Logika polega na tym, że forma "notacji kropkowej" z $istnieje testuje obecność elementu pod określonym indeksem ( n-1 ) i musi on tam być, aby tablica miała przynajmniej taką długość.

Reszta zawężenia idealnie wykorzystuje $ setIntersection metoda w celu zwrócenia pasujących elementów między tablicą rzeczywistą a tablicą testowaną. Ponieważ tablica w dokumencie nie odpowiada strukturze „tablicy testowej”, należy ją przekształcić za pomocą $map operacja, która jest ustawiona na zwracanie tylko pola „artist” z każdego elementu tablicy.

Gdy tworzone jest „przecięcie” tych dwóch tablic, jest ono ostatecznie testowane pod kątem $rozmiar z otrzymanej listy wspólnych elementów, w których zastosowano test, aby sprawdzić, czy „co najmniej trzy” z tych elementów okazały się wspólne.

Na koniec po prostu „odfiltrowujesz” wszystko, co nie było prawdą, używając $match stan.

Idealnie byłoby używać MongoDB w wersji 2.6 lub nowszej, aby mieć dostęp do tych operatorów. W przypadku wcześniejszych wersji 2.2.xi 2.4.x jest to nadal możliwe, ale tylko trochę więcej pracy i narzutu na przetwarzanie:

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 } }}
])



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Jak posortować tablicę w kolekcji

  2. Mongo $addToUstaw tablicę

  3. Agregacja MongoDB:Jak uzyskać całkowitą liczbę rekordów?

  4. Instalowanie MongoDB na CentOS 8

  5. Mongodb Aggregation Framework:Czy $group używa indeksu?