Vaše otázka má pro mě dvě možnosti, ale možná nějaké vysvětlení, abyste mohli začít.
Nejprve vám musím vysvětlit, že nerozumíte záměru $elemMatch
a v tomto případě je to zneužito.
Myšlenka $elemMatch
je vytvořit "dotazový dokument", který je ve skutečnosti aplikován na prvky pole. Záměrem je, kde máte "více podmínek" na dokumentu v rámci pole, aby se diskrétně shodoval v rámci členského dokumentu, a ne v rámci celého pole vnějšího dokumentu. tj.:
{
"data": [
{ "a": 1, "b": 3 },
{ "a": 2, "b": 2 }
]
}
A následující dotaz bude fungovat, i když žádný skutečný jednotlivý prvek v tomto poli neodpovídá, ale celý dokument ano:
db.collection.find({ "data.a": 1, "data.b": 2 })
Chcete-li však zkontrolovat, zda skutečný prvek odpovídá oběma těmto podmínkám, použijte $elemMatch
:
db.collection.find({ "data": { "a": 1, "b": 2 } })
V tomto vzorku tedy žádná shoda a bude se shodovat pouze tam, kde konkrétní prvek pole měl oba tyto prvky.
Nyní máme $elemMatch
vysvětleno, zde je váš zjednodušený dotaz:
db.collection.find({ "tracks.artist": { "$in": arr } })
Mnohem jednodušší a funguje to tak, že se podíváte na všechny členy pole podle jednoho pole a vrátíte se tam, kde jakýkoli prvek v dokumentu obsahuje alespoň jeden z těchto možných výsledků.
Ale ne to, na co se ptáte, tak dále s vaší otázkou. Pokud si přečtete toto poslední prohlášení, měli byste si uvědomit, že $in
je ve skutečnosti $or
stav. Je to jen zkrácená forma pro dotaz na „nebo“ na stejný prvek v dokumentu.
S ohledem na to je jádrem toho, co žádáte, "a" operace, kde jsou obsaženy všechny "tři" hodnoty. Za předpokladu, že jste v testu posílali pouze „tři“ položky, můžete použít formu $and
který je ve zkrácené podobě $all
:
db.collection.find({ "tracks.artist": { "$all": arr } })
To by vám vrátilo pouze dokumenty, které měly prvek v rámci členů tohoto pole odpovídající "všem" prvkům zadaným v testovací podmínce. To může být to, co chcete, ale existuje případ, kdy samozřejmě chcete zadat seznam řekněme „čtyř nebo více“ umělců k testování a chcete z toho pouze „tři“ nebo nějaké menší číslo, v tom případě $all
operátor je příliš stručný.
Existuje však logický způsob, jak to vyřešit, jen to vyžaduje trochu více zpracování s operátory, které nejsou dostupné pro základní dotazy, ale které jsou dostupné pro rámec agregace :
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 } }
])
První fáze implementuje standardní dotaz $match
podmínku, abyste vyfiltrovali dokumenty pouze na ty, které „pravděpodobně“ budou odpovídat podmínkám. Logickým případem je použití $in
stejně jako dříve najde ty dokumenty, kde je alespoň jeden z prvků přítomných ve vašem "testovacím" poli přítomen alespoň v jednom z členských polí ve vlastním poli dokumentů.
Další klauzule je něco, co byste v ideálním případě měli vytvářet v kódu, protože se týká „délky“ pole. Myšlenka je, že chcete alespoň „tři“ shody, pak pole, které testujete v dokumentu, musí mít alespoň „tři“ prvky, aby to splnilo, takže nemá smysl získávat dokumenty se „dvěma“ nebo méně prvky pole. protože se nikdy nemohou shodovat se „tři“.
Vzhledem k tomu, že všechny dotazy MongoDB jsou v podstatě pouze reprezentací datové struktury, je to velmi snadné sestavit. tj. pro JavaScript:
var matchCount = 3; // how many matches we want
var match1 = { "$match": { "tracks.artist": { "$in": arr } } };
match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };
Logika je taková, že „tečkový zápis“ tvoří $exists
testuje přítomnost prvku na zadaném indexu ( n-1 ) a musí tam být, aby pole mělo alespoň tuto délku.
Zbytek zúžení ideálně používá $setIntersection
metoda, aby se vrátily odpovídající prvky mezi skutečným polem a testovaným polem. Protože pole v dokumentu neodpovídá struktuře pro "testovací pole", je třeba jej transformovat pomocí $map
operace, která je nastavena tak, aby vracela pouze pole "artist" z každého prvku pole.
Protože je vytvořen "průnik" těchto dvou polí, je nakonec testován pro $size
výsledného seznamu společných prvků, kde je test aplikován, aby se zjistilo, že "nejméně tři" z těchto prvků byly společné.
Nakonec jednoduše „odfiltrujete“ vše, co nebyla pravda, pomocí $match
podmínka.
V ideálním případě používáte MongoDB 2.6 nebo vyšší, abyste měli tyto operátory k dispozici. U dřívějších verzí 2.2.xa 2.4.x je to stále možné, ale jen trochu více práce a režie zpracování:
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 } }}
])