MongoDB:使用数学运算符搜索文本字段
MongoDB: Searching a text field using mathematical operators
我在 MongoDB 中有如下文件 -
[
{
"_id": "17tegruebfjt73efdci342132",
"name": "Test User1",
"obj": "health=8,type=warrior",
},
{
"_id": "wefewfefh32j3h42kvci342132",
"name": "Test User2",
"obj": "health=6,type=magician",
}
.
.
]
我想 运行 查询说 health>6
并且它应该 return "Test User1"
条目。 obj
键被索引为文本字段,因此我可以执行 {$text:{$search:"health=8"}}
以获得精确匹配,但我正在尝试将数学运算符合并到搜索中。
我知道 $gt
和 $lt
运算符,但是,在这种情况下不能使用它,因为 health
不是文档的键。最简单的方法是确定 health
成为文档的关键,但由于某些限制,我无法更改文档结构。
有什么办法可以实现吗?我知道 mongo 支持 运行ning javascript 代码,不确定在这种情况下是否有帮助。
我认为在 $text
搜索索引中不可能,但您可以使用聚合查询将对象条件转换为对象数组,
$split
将 obj
拆分为 ",",它将 return 一个数组
$map
迭代上述拆分结果数组的循环
$split
用“=”拆分当前条件,它将 return 一个数组
$let
声明变量cond
存放上面拆分结果的结果
$first
到 return 上述拆分结果中的第一个元素 k
作为条件键
$last
到 return 上述拆分结果中的最后一个元素 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
拆分为 ",",它将 return 一个数组
$map
迭代上述拆分结果数组的循环
$split
用“=”拆分当前条件,它将 return 一个数组
- 现在我们已经准备好一个嵌套的字符串条件数组:
"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" }
])
使用 JavaScript 是做你想做的事情的一种方式。下面是一个 find
,它通过查找具有 health=
文本后跟一个整数的文档来使用 obj
上的索引(如果需要,您可以将其与 ^
锚定在正则表达式)。
然后使用 JavaScript 函数在通过 health=
部分进行子字符串化后解析出实际整数,执行 parseInt
获取 int,然后进行比较operator/value你在问题中提到了
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;
}
})
您当然可以根据自己的喜好调整它以使用其他运算符。
NOTE: I'm using the JavaScript regex capability, which may not be supported by MongoDB. I used Mongo-Shell r4.2.6 where it is supported. If that's the case, in the JavaScript, you will have to extract the integer out a different way.
我提供了一个 Mongo Playground 供您试用,如果您想对其进行调整,但您会得到
Invalid query:
Line 3: Javascript regex are not supported. Use "$regex" instead
直到您更改它以解决上述正则表达式问题。尽管如此,如果您使用的是最新最好的,这不应该是一个限制。
性能
Disclaimer: This analysis is not rigorous.
我 运行 在 MongoDB Compass 中使用 Explain Plan 针对一个小集合进行两次查询(更大的集合可能会导致不同的结果)。第一个查询是上面那个;第二个是相同的查询,但删除了 obj
过滤器。
和
如您所见,计划有所不同。第一次查询检查的文档数量较少,第一次查询使用了索引。
执行时间没有意义,因为集合很小。结果似乎与 documentation 一致,但文档本身似乎有点不一致。这里有两个摘录
Use the $where
operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system. The $where
provides greater flexibility, but requires that the database processes the JavaScript expression or function for each document in the collection.
和
Using normal non-$where
query statements provides the following performance advantages:
- MongoDB will evaluate non-
$where
components of query before $where
statements. If the non-$where
statements match no documents, MongoDB will not perform any query evaluation using $where
.
- The non-
$where
query statements may use an index.
我不太确定这是怎么回事,TBH。作为通用解决方案,它可能很有用,因为您似乎可以生成可以处理所有运算符的查询。
我在 MongoDB 中有如下文件 -
[
{
"_id": "17tegruebfjt73efdci342132",
"name": "Test User1",
"obj": "health=8,type=warrior",
},
{
"_id": "wefewfefh32j3h42kvci342132",
"name": "Test User2",
"obj": "health=6,type=magician",
}
.
.
]
我想 运行 查询说 health>6
并且它应该 return "Test User1"
条目。 obj
键被索引为文本字段,因此我可以执行 {$text:{$search:"health=8"}}
以获得精确匹配,但我正在尝试将数学运算符合并到搜索中。
我知道 $gt
和 $lt
运算符,但是,在这种情况下不能使用它,因为 health
不是文档的键。最简单的方法是确定 health
成为文档的关键,但由于某些限制,我无法更改文档结构。
有什么办法可以实现吗?我知道 mongo 支持 运行ning javascript 代码,不确定在这种情况下是否有帮助。
我认为在 $text
搜索索引中不可能,但您可以使用聚合查询将对象条件转换为对象数组,
$split
将obj
拆分为 ",",它将 return 一个数组$map
迭代上述拆分结果数组的循环$split
用“=”拆分当前条件,它将 return 一个数组$let
声明变量cond
存放上面拆分结果的结果$first
到 return 上述拆分结果中的第一个元素k
作为条件键$last
到 return 上述拆分结果中的最后一个元素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
拆分为 ",",它将 return 一个数组$map
迭代上述拆分结果数组的循环$split
用“=”拆分当前条件,它将 return 一个数组- 现在我们已经准备好一个嵌套的字符串条件数组:
"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" }
])
使用 JavaScript 是做你想做的事情的一种方式。下面是一个 find
,它通过查找具有 health=
文本后跟一个整数的文档来使用 obj
上的索引(如果需要,您可以将其与 ^
锚定在正则表达式)。
然后使用 JavaScript 函数在通过 health=
部分进行子字符串化后解析出实际整数,执行 parseInt
获取 int,然后进行比较operator/value你在问题中提到了
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;
}
})
您当然可以根据自己的喜好调整它以使用其他运算符。
NOTE: I'm using the JavaScript regex capability, which may not be supported by MongoDB. I used Mongo-Shell r4.2.6 where it is supported. If that's the case, in the JavaScript, you will have to extract the integer out a different way.
我提供了一个 Mongo Playground 供您试用,如果您想对其进行调整,但您会得到
Invalid query:
Line 3: Javascript regex are not supported. Use "$regex" instead
直到您更改它以解决上述正则表达式问题。尽管如此,如果您使用的是最新最好的,这不应该是一个限制。
性能
Disclaimer: This analysis is not rigorous.
我 运行 在 MongoDB Compass 中使用 Explain Plan 针对一个小集合进行两次查询(更大的集合可能会导致不同的结果)。第一个查询是上面那个;第二个是相同的查询,但删除了 obj
过滤器。
和
如您所见,计划有所不同。第一次查询检查的文档数量较少,第一次查询使用了索引。
执行时间没有意义,因为集合很小。结果似乎与 documentation 一致,但文档本身似乎有点不一致。这里有两个摘录
Use the
$where
operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system. The$where
provides greater flexibility, but requires that the database processes the JavaScript expression or function for each document in the collection.
和
Using normal non-
$where
query statements provides the following performance advantages:
- MongoDB will evaluate non-
$where
components of query before$where
statements. If the non-$where
statements match no documents, MongoDB will not perform any query evaluation using$where
.- The non-
$where
query statements may use an index.
我不太确定这是怎么回事,TBH。作为通用解决方案,它可能很有用,因为您似乎可以生成可以处理所有运算符的查询。