繁体   English   中英

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 ,它应该返回"Test User1"条目。 obj键被索引为文本字段,因此我可以执行{$text:{$search:"health=8"}}来获得完全匹配,但我正在尝试将数学运算符合并到搜索中。

我知道$gt$lt运算符,但是,在这种情况下不能使用它,因为health不是文档的关键。 最简单的方法是让health成为文档的关键,但由于某些限制,我无法更改文档结构。

无论如何这可以实现吗? 我知道 mongo 支持运行 javascript 代码,不确定在这种情况下是否有帮助。

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

  • $splitobj $split为“,”,它将返回一个数组
  • $map迭代上述拆分结果数组的循环
  • $split通过“=”分割当前条件,它将返回一个数组
  • $let声明变量cond来存放上述拆分结果
  • $first到从上述分割结果中返回的第一个元素k为条件的关键
  • $last到最后一个元件从上述分割结果中返回v为条件的值
  • 现在我们已经准备好了字符串条件的对象数组:
  "objTransform": [
    { "k": "health", "v": "9" },
    { "k": "type", "v": "warrior" }
  ]
  • 使用$elemMatch在同一对象中匹配键和值的$match条件
  • $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" }
])

操场


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

  • $splitobj $split为“,”,它将返回一个数组
  • $map迭代上述拆分结果数组的循环
  • $split通过“=”分割当前条件,它将返回一个数组
  • 现在我们已经准备好了一个嵌套的字符串条件数组:
  "objTransform": [
    ["type", "warrior"],
    ["health", "9"]
  ]
  • 使用$elemMatch在数组元素中匹配键和值的$match条件,“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,然后是您在问题中提到的比较运算符/值。

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 正则表达式功能。 我用蒙戈壳牌r4.2.6那里支持。 如果是这种情况,在 JavaScript 中,您将不得不以不同的方式提取整数。

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

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查询语句可能使用索引。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM