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 搜索索引中不可能,但您可以使用聚合查询将对象条件转换为对象数组,

  • $splitobj 拆分为 ",",它将 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" }
])

Playground


上述聚合查询的第二个升级版本,如果可以在您的客户端管理,则在条件转换中做更少的操作,

  • $splitobj 拆分为 ",",它将 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" }
])

Playground

使用 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。作为通用解决方案,它可能很有用,因为您似乎可以生成可以处理所有运算符的查询。