Основная концепция здесь состоит в том, чтобы искать вещи, которых нет в списке возможных значений для каждого элемента массива, а затем «исключать» этот документ.Что означает использование $elemMatch
с $nin
для списка и $not
для изменения логики:
db.collection.find({
"arrayProperty": {
"$not": { "$elemMatch": { "$nin": ["1", "2", "3", "4"] } }
}
})
Какиевозвращает правильные документы:
{ "_id" : "1", "arrayProperty" : [ "1", "2" ] }
{ "_id" : "2", "arrayProperty" : [ "1", "4" ] }
Это фактически использует собственные операторы в «обработчике запросов» для оценки выражения, а не «принудительное вычисление» через$expr
или $where
, о которых мы поговорим позже.Это правильные результаты, но единственной проблемой здесь является то, что шаблон оператора фактически сводит на нет использование любого индекса.К счастью, мы можем с этим что-то сделать:
db.collection.find({
"arrayProperty": {
"$in": ["1", "2", "3", "4"],
"$not": { "$elemMatch": { "$nin": ["1", "2", "3", "4"] } }
}
})
Поначалу это может показаться странным, но добавление $in
здесь является допустимым условием.То, что он делает для запроса, заключается в том, чтобы индекс фактически использовался при выборе действительных документов.В примере вопроса, который по-прежнему представляет собой «ВСЕ» из представленных документов, но в реальном мире не все вещи обычно соответствуют списку аргументов.
По существу, это изменяет условия разбора запроса:
"winningPlan" : { "stage" : "COLLSCAN"
На это:
"winningPlan" : { "stage" : "FETCH",
"inputStage" : { "stage" : "IXSCAN",
Это делает $in
стоящимФильтр для добавления к выражению и выражение "native query operator" - самый быстрый способ сделать это.
Проблема с $expr
(кромедоступно только из MongoDB 3.6), это означает, что необходимо отсканировать «всю коллекцию», чтобы применить содержащееся в ней «выражение оператора агрегирования».Конечно, мы также только что узнали, что $in
добавляет к запросу
db.collection.find({
"arrayProperty": { "$in": ["1", "2", "3", "4"] },
"$expr": { "$setIsSubset": ["$arrayProperty", ["1", "2", "3", "4"]] }
})
Это имеет аналогичный IXSCAN
вход, где присутствует индекс из-за $in
и только с использованием логического условия $setIsSubset
для отклонения других документов, найденных в выборе индекса.
Более ранние формы использования с предыдущей версией MongoDB менее идеальны:
db.collection.aggregate([
{ "$match": { "$in": ["1", "2", "3", "4"] } },
{ "$redact": {
"if": { "$setIsSubset": ["$arrayProperty", ["1", "2", "3", "4"]] },
"then": "$$KEEP",
"else": "$$PRUNE"
}}
])
Или с использованием $where
:
db.collection.find({
"arrayProperty": { "$in": ["1", "2", "3", "4"] },
"$where": function() {
return this.arrayProperty.every(a => ["1", "2", "3", "4"].some(s => a === s))
}
})
Таким образом, все фактически выполняют свою работу, кроме комбинации $elemMatch
с $nin
и $not
, включая оператор $in
для выбора индекса, действительно то, что вам действительно нужно.И это поддерживается во всех версиях.