Запрос для поиска документов, в которых все элементы массива находятся в массиве - PullRequest
0 голосов
/ 25 мая 2018

У меня есть коллекция с такими документами:

{
_id : "1",
arrayProperty : ["1","2","3"]
}

Я хочу найти документы, в которых есть все элементы arrayProperty, содержащиеся в некотором массиве.

Предположим, у меня есть эта коллекция:

{_id : "1", arrayProperty : ["1", "2"]}
{_id : "2", arrayProperty : ["1", "4"]}
{_id : "3", arrayProperty : ["1", "7", "8"]}
{_id : "4", arrayProperty : ["1", "9"]}

и я хочу найти документы, которые содержат все свои элементы arrayProperty, содержащиеся в ["1", "2", "3", "4"]

Он должен вернуть:

{_id : "1", arrayProperty : ["1", "2"]}
{_id : "2", arrayProperty : ["1", "4"]}

1 Ответ

0 голосов
/ 26 мая 2018

Основная концепция здесь состоит в том, чтобы искать вещи, которых нет в списке возможных значений для каждого элемента массива, а затем «исключать» этот документ.Что означает использование $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 для выбора индекса, действительно то, что вам действительно нужно.И это поддерживается во всех версиях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...