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