Вашият въпрос има две възможности за мен, но може би някакво обяснение, за да започнете.
Преди всичко трябва да ви обясня, че не разбирате намерението на $elemMatch
и в този случай се злоупотребява.
Идеята на $elemMatch
е да се създаде "документ за заявка", който всъщност се прилага към елементите на масива. Намерението е, когато имате "множество условия" за документ в рамките на масива, за да го съпоставите дискретно в членския документ, а не в целия масив на външния документ. т.е.:
{
"data": [
{ "a": 1, "b": 3 },
{ "a": 2, "b": 2 }
]
}
И следната заявка ще работи, въпреки че нито един действителен елемент в този масив не съвпада, но целият документ съвпада:
db.collection.find({ "data.a": 1, "data.b": 2 })
Но за да проверите дали действителен елемент отговаря и на двете условия, тук използвате $elemMatch
:
db.collection.find({ "data": { "a": 1, "b": 2 } })
Така че няма съвпадение в тази извадка и ще съвпадне само там, където конкретен елемент от масива има и двата елемента.
Сега имаме $elemMatch
обяснено, ето вашата опростена заявка:
db.collection.find({ "tracks.artist": { "$in": arr } })
Много по-просто и работи, като разглежда всички членове на масива по едно поле и връща където всеки елемент в документа съдържа поне един от тези възможни резултати.
Но не това, което питаш, така че с въпроса си. Ако прочетете последното твърдение, трябва да разберете, че $in
всъщност е $or
състояние. Това е просто съкратена форма за питане на „или“ над същия елемент в документа.
Имайки това предвид, в основата на това, което питате, е „и“ операция, където се съдържат всичките "три" стойности. Ако приемем, че изпращате само „три“ елемента в теста, тогава можете да използвате форма на $and
което е в съкратената форма на $allкод>
:
db.collection.find({ "tracks.artist": { "$all": arr } })
Това ще ви върне само документите, в които елементът в членовете на този масив съответства на "всички" елементи, посочени в тестовото условие. Това може и да е това, което искате, но има случай, в който, разбира се, искате да посочите списък от да речем „четирима или повече“ изпълнители за тестване и искате само „трима“ или някакъв по-малък брой от тях, в който случай $all
операторът е твърде кратък.
Но има логичен начин за решаване на това, просто отнема малко повече обработка с оператори, които не са достъпни за основните заявки, но са достъпни за рамка за агрегиране :
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 } }
])
Първият етап изпълнява стандартна заявка $matchкод>
условие, за да филтрирате документите само към тези, които е „вероятно“ да отговарят на условията. Логичният случай тук е да използвате $inкод>
както преди, той ще намери тези документи, където поне един от елементите, присъстващи във вашия "тестов" масив, присъства в рамките на поне едно от полетата на член в собствения масив на документи.
Следващата клауза е нещо, което в идеалния случай трябва да вграждате в код, тъй като се отнася до „дължината“ на масива. Идеята тук е, когато искате поне „три“ съвпадения, тогава масивът, който тествате в документа, трябва да има поне „три“ елемента, за да изпълни това, така че няма смисъл да извличате документи с „два“ или по-малко елемента от масив тъй като те никога не могат да съпоставят "три".
Тъй като всички MongoDB заявки са по същество само представяне на структура от данни, това прави това много лесно за изграждане. т.е. за JavaScript:
var matchCount = 3; // how many matches we want
var match1 = { "$match": { "tracks.artist": { "$in": arr } } };
match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };
Логиката там е, че формата "точкова нотация" с $съществува
тества наличието на елемент на посочения индекс ( n-1 ) и той трябва да е там, за да има масивът поне с тази дължина.
Останалата част от стесняването в идеалния случай използва $ setIntersection
метод, за да върне съвпадащите елементи между действителния масив и тествания масив. Тъй като масивът в документа не съответства на структурата за "тестовия масив", той трябва да бъде трансформиран чрез $map
операция, която е настроена да връща само полето "изпълнител" от всеки елемент на масива.
Тъй като се прави "пресичането" на тези два масива, то накрая се тества за $size
от получения списък с общи елементи, където тестът е приложен, за да се види, че „поне три“ от тези елементи са установени като общи.
Накрая просто „филтрирате“ всичко, което не е вярно, като използвате $match
състояние.
В идеалния случай използвате MongoDB 2.6 или по-нова версия, за да имате налични тези оператори. За по-ранните версии на 2.2.x и 2.4.x все още е възможно, но само малко повече работа и обработка:
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 } }}
])