【问题标题】:MongoDB: Searching a text field using mathematical operatorsMongoDB:使用数学运算符搜索文本字段
【发布时间】:2021-06-30 00:17:00
【问题描述】:

我在 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 代码,不确定在这种情况下是否有帮助。

【问题讨论】:

  • 您可以尝试在聚合查询中转换数据,例如提取适当的子字符串并通过它们进行匹配/搜索(因为您已经提到更改数据设计不是一种选择)。但是,我认为这不会是一个非常有效的搜索。

标签: string mongodb search operators


【解决方案1】:

我认为在$text 搜索索引中不可能,但您可以使用聚合查询将对象条件转换为对象数组,

  • $splitobj 拆分为 "," 会返回一个数组
  • $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" }
])

Playground


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

  • $splitobj 拆分为 "," 会返回一个数组
  • $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" }
])

Playground

【讨论】:

    【解决方案2】:

    使用 JavaScript 是做你想做的事的一种方式。下面是一个find,它使用obj 上的索引,通过查找具有health= 文本后跟一个整数的文档(如果需要,可以在正则表达式中使用^ 锚定它)。

    然后,它使用 JavaScript 函数解析出实际整数,然后将您的方式通过health= 部分,执行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;
        }
    })
    

    您当然可以根据自己的喜好对其进行调整以使用其他运算符。

    注意:我正在使用 MongoDB 可能不支持的 JavaScript 正则表达式功能。我使用 支持的 Mongo-Shell r4.2.6。如果是这种情况,在 JavaScript 中,您将不得不以不同的方式提取整数。

    我提供了一个Mongo Playground 来尝试一下,如果你想调整它,但你会得到

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

    直到您更改它以解决上述正则表达式问题。不过,如果您使用的是最新最好的,这不应该是一个限制。

    性能

    免责声明:此分析严谨。

    我使用 MongoDB Compass 中的 Explain Plan 对一个小集合(一个更大的集合可能导致不同的结果)运行了两个查询。第一个查询是上面的那个;第二个是相同的查询,但删除了 obj 过滤器。

    如您所见,计划有所不同。第一个查询检查的文档数较少,第一个查询使用索引。

    执行时间没有意义,因为集合很小。结果似乎与documentation 一致,但文档本身似乎有点不一致。这里摘录两段

    使用 $where 运算符将包含 JavaScript 表达式的字符串或完整的 JavaScript 函数传递给查询系统。 $where 提供了更大的灵活性,但要求数据库为集合中的 每个 文档处理 JavaScript 表达式或函数。

    使用普通的非$where 查询语句具有以下性能优势:

    • MongoDB 将在$where 语句之前评估非$where 查询组件。如果非$where 语句不匹配任何文档,MongoDB 将不会使用$where 执行任何查询评估。
    • $where 查询语句可以使用索引。

    我不完全确定该怎么做,TBH。作为通用解决方案,它可能很有用,因为您似乎可以生成可以处理所有运算符的查询。

    【讨论】:

    • 谢谢,您知道按照您的建议直接使用 javascript 对性能的影响吗?我读过使用 JavaScript 会强制查询在所有文档(而不是索引)上运行。
    • @m0bi5,当然。我更新了答案。看久一点很有趣……
    • 是的,文档中的“..JavaScript 表达式或集合中每个文档的函数..”让我感到困惑。但似乎并非如此。非常感谢!这就是我一直在寻找的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多