MongoDB: Поиск текстового поля с использованием математических операторов

#string #mongodb #search #operators

Вопрос:

У меня есть документы в MongoDB, как показано ниже —

 [
{
    "_id": "17tegruebfjt73efdci342132",
    "name": "Test User1",
    "obj":  "health=8,type=warrior",
},
{
    "_id": "wefewfefh32j3h42kvci342132",
    "name": "Test User2",
    "obj":  "health=6,type=magician",
}
.
.
]
 

Я хочу запустить запрос, скажем health>6 , и он должен вернуть "Test User1" запись. obj Ключ индексируется в виде текстового поля, поэтому я могу {$text:{$search:"health=8"}} получить точное совпадение, но я пытаюсь включить математические операторы в поиск.

Я знаю об операторах $gt и $lt , однако, он не может быть использован в данном случае, так как health не является ключом документа. Самый простой выход-это сделать health ключ документа наверняка, но я не могу изменить структуру документа из-за определенных ограничений.

Есть ли вообще возможность этого достичь? Я знаю, что mongo поддерживает запуск кода javascript, но не уверен, что это может помочь в данном случае.

Комментарии:

1. Вы можете попробовать преобразовать данные в запросе агрегации, например, извлечь соответствующие подстроки и сопоставить/выполнить поиск по ним (поскольку вы уже упоминали, что изменение структуры данных не является опцией). Но, я думаю, это будет не очень эффективный поиск.

Ответ №1:

Я не думаю, что это возможно в $text поисковом индексе, но вы можете преобразовать условия вашего объекта в массив объектов с помощью запроса агрегации,

  • $split разделить obj на»,», и он вернет массив
  • $map для цикла итерации приведенного выше массива результатов разделения
  • $split чтобы разделить текущее состояние на»=», и оно вернет массив
  • $let чтобы объявить переменную cond для хранения результата приведенного выше результата разделения
  • $first чтобы вернуть первый элемент из приведенного выше разбиения, введите в k качестве ключа условия
  • $last чтобы вернуть последний элемент из приведенного выше разбиения, введите в v качестве значения условия
  • теперь у нас есть готовый массив объектов строковых условий:
   "objTransform": [
    { "k": "health", "v": "9" },
    { "k": "type", "v": "warrior" }
  ]
 
  • $match условие соответствия ключа и значения в одном и том же объекте с помощью $elemMatch
  • $unset чтобы удалить массив преобразования objTransform , потому что он не нужен
 db.collection.aggregate([
  {
    $addFields: {
      objTransform: {
        $map: {
          input: { $split: ["$obj", ","] },
          in: {
            $let: {
              vars: {
                cond: { $split: ["$this", "="] }
              },
              in: {
                k: { $first: "$cond" },
                v: { $last: "$cond" }
              }
            }
          }
        }
      }
    }
  },
  {
    $match: {
      objTransform: {
        $elemMatch: {
          k: "health",
          v: { $gt: "8" }
        }
      }
    }
  },
  { $unset: "objTransform" }
])
 

Игровая площадка


Вторая обновленная версия вышеупомянутого запроса агрегирования позволяет выполнять меньше операций при преобразовании условий, если это возможно для управления на стороне клиента.,

  • $split разделить obj на»,», и он вернет массив
  • $map для цикла итерации приведенного выше массива результатов разделения
  • $split чтобы разделить текущее состояние на»=», и оно вернет массив
  • теперь у нас есть готовый вложенный массив строковых условий:
   "objTransform": [
    ["type", "warrior"],
    ["health", "9"]
  ]
 
  • $match условие для соответствия ключа и значения в элементе массива с использованием $elemMatch «0» для соответствия первой позиции массива и «1» для соответствия второй позиции массива
  • $unset чтобы удалить массив преобразования objTransform , потому что он не нужен
 db.collection.aggregate([
  {
    $addFields: {
      objTransform: {
        $map: {
          input: { $split: ["$obj", ","] },
          in: { $split: ["$this", "="] }
        }
      }
    }
  },
  {
    $match: {
      objTransform: {
        $elemMatch: {
          "0": "health",
          "1": { $gt: "8" }
        }
      }
    }
  },
  { $unset: "objTransform" }
])
 

Игровая площадка

Ответ №2:

Использование JavaScript-это один из способов сделать то, что вы хотите. Ниже приведен a find , который использует индекс obj , находя документы, в которых health= за текстом следует целое число (если вы хотите, вы можете закрепить это ^ в регулярном выражении).

Затем он использует функцию JavaScript для анализа фактического целого числа после подстроки, пройдя health= часть, выполнив a parseInt , чтобы получить int, а затем оператор сравнения/значение, которое вы упомянули в вопросе.

 db.collection.find({
    // use the index on obj to potentially speed up the query
    "obj":/health=d /,
    // now apply a function to narrow down and do the math
    $where: function() {
        var i = this.obj.indexOf("health=")   7;
        var s = this.obj.substring(i);
        var m = s.match(/d /);
        
        if (m)
            return parseInt(m[0]) > 6;       
        return false;
    }
})
 

Вы, конечно, можете настроить его так, как вам заблагорассудится, чтобы использовать других операторов.

ПРИМЕЧАНИЕ: Я использую функцию регулярных выражений JavaScript, которая может не поддерживаться MongoDB. Я использовал Mongo-оболочку r4.2.6, где она поддерживается. Если это так, то в JavaScript вам придется извлечь целое число другим способом.


Я предоставил игровую площадку Mongo, чтобы опробовать ее, если вы хотите ее настроить, но вы получите

 Invalid query:

Line 3: Javascript regex are not supported. Use "$regex" instead
 

пока вы не измените его для учета проблемы с регулярным выражением, указанной выше. Тем не менее, если вы используете самые последние и лучшие, это не должно быть ограничением.

Производительность

Отказ от ответственности: Этот анализ не является строгим.

Я выполнил два запроса к небольшой коллекции (возможно, больший из них мог привести к другим результатам) с помощью плана объяснения в MongoDB Compass. Первый запрос соответствует приведенному выше; второй — тот же запрос, но с obj удаленным фильтром.

введите описание изображения здесь

и

введите описание изображения здесь

Как вы можете видеть, планы разные. Количество проверенных документов меньше для первого запроса, и первый запрос использует индекс.

Время выполнения не имеет смысла, потому что коллекция невелика. Результаты, похоже, согласуются с документацией, но документация, похоже, немного расходится сама с собой. Вот два отрывка

Используйте $where оператор для передачи либо строки, содержащей выражение JavaScript, либо полной функции JavaScript в систему запросов. Это $where обеспечивает большую гибкость, но требует, чтобы база данных обрабатывала выражение или функцию JavaScript для каждого документа в коллекции.

и

Использование обычных операторов, не связанных с $where запросами, обеспечивает следующие преимущества в производительности:

  • MongoDB будет оценивать $where некомпоненты запроса до $where инструкций. Если $where инструкции не соответствуют никаким документам, MongoDB не будет выполнять оценку запросов с помощью $where .
  • Инструкции, не $where содержащие запросов, могут использовать индекс.

Я не совсем уверен, что с этим делать, ТБХ. В качестве общего решения это может быть полезно, потому что, похоже, вы можете создавать запросы, которые могут обрабатывать все ваши операторы.

Комментарии:

1. Спасибо, вы случайно не знаете, как влияет на производительность прямое использование javascript, как вы предлагали? Я читал, что использование JavaScript заставляет запрос выполняться для всех документов (вместо индекса).

2. @m0bi5, конечно. Я обновил ответ. Было интересно взглянуть подольше…

3. Да, «..выражение или функция JavaScript для каждого документа в коллекции..» в документации меня смутило. Но, похоже, это не так. Большое спасибо! Это то, что я искал