#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 для каждого документа в коллекции..» в документации меня смутило. Но, похоже, это не так. Большое спасибо! Это то, что я искал