使用 MongoDB 搜索实现自动完成功能

2024-03-15

我有一个MongoDB表格文件的收集

{
    "id": 42,
    "title": "candy can",
    "description": "canada candy canteen",
    "brand": "cannister candid",
    "manufacturer": "candle canvas"
}

我需要通过匹配字段来实现基于输入搜索词的自动完成功能,除了id。例如,如果输入项是can,那么我应该返回所有匹配的words在文档中为

{ hints: ["candy", "can", "canada", "canteen", ...]

我在看这个问题 https://stackoverflow.com/questions/8223841/implement-autocomplete-on-mongodb但这没有帮助。我也尝试搜索如何做regex在多个字段中搜索并提取匹配的标记,或者在 MongoDB 中提取匹配的标记text search但找不到任何帮助。


tl;dr

对于您想要的内容没有简单的解决方案,因为普通查询无法修改它们返回的字段。有一个解决方案(使用下面的 mapReduce 内联而不是输出到集合),但除了非常小的数据库之外,不可能实时执行此操作。

问题

正如所写,普通查询无法真正修改它返回的字段。但还有其他问题。如果您想在一半的时间内进行正则表达式搜索,则必须建立索引all字段,这需要不成比例的 RAM 来实现该功能。如果你不索引all字段,正则表达式搜索会导致采集扫描 http://docs.mongodb.org/manual/core/indexes-introduction/#index-introduction,这意味着每个文档都必须从磁盘加载,这将花费太多时间来方便自动完成。此外,多个同时请求自动完成的用户会给后端带来相当大的负载。

解决方案

问题非常类似于我已经回答过的一个 https://stackoverflow.com/a/29525482/1296707:我们需要从多个字段中提取每个单词,删除停用词 https://en.wikipedia.org/wiki/Stop_words并将剩余的单词与在集合中找到的单词的相应文档的链接一起保存。现在,为了获取自动完成列表,我们只需查询索引单词列表即可。

第 1 步:使用 Map/Reduce 作业提取单词

db.yourCollection.mapReduce(
  // Map function
  function() {

    // We need to save this in a local var as per scoping problems
    var document = this;

    // You need to expand this according to your needs
    var stopwords = ["the","this","and","or"];

    for(var prop in document) {

      // We are only interested in strings and explicitly not in _id
      if(prop === "_id" || typeof document[prop] !== 'string') {
        continue
      }

      (document[prop]).split(" ").forEach(
        function(word){

          // You might want to adjust this to your needs
          var cleaned = word.replace(/[;,.]/g,"")

          if(
            // We neither want stopwords...
            stopwords.indexOf(cleaned) > -1 ||
            // ...nor string which would evaluate to numbers
            !(isNaN(parseInt(cleaned))) ||
            !(isNaN(parseFloat(cleaned)))
          ) {
            return
          }
          emit(cleaned,document._id)
        }
      ) 
    }
  },
  // Reduce function
  function(k,v){

    // Kind of ugly, but works.
    // Improvements more than welcome!
    var values = { 'documents': []};
    v.forEach(
      function(vs){
        if(values.documents.indexOf(vs)>-1){
          return
        }
        values.documents.push(vs)
      }
    )
    return values
  },

  {
    // We need this for two reasons...
    finalize:

      function(key,reducedValue){

        // First, we ensure that each resulting document
        // has the documents field in order to unify access
        var finalValue = {documents:[]}

        // Second, we ensure that each document is unique in said field
        if(reducedValue.documents) {

          // We filter the existing documents array
          finalValue.documents = reducedValue.documents.filter(

            function(item,pos,self){

              // The default return value
              var loc = -1;

              for(var i=0;i<self.length;i++){
                // We have to do it this way since indexOf only works with primitives

                if(self[i].valueOf() === item.valueOf()){
                  // We have found the value of the current item...
                  loc = i;
                  //... so we are done for now
                  break
                }
              }

              // If the location we found equals the position of item, they are equal
              // If it isn't equal, we have a duplicate
              return loc === pos;
            }
          );
        } else {
          finalValue.documents.push(reducedValue)
        }
        // We have sanitized our data, now we can return it        
        return finalValue

      },
    // Our result are written to a collection called "words"
    out: "words"
  }
)

针对您的示例运行此 mapReduce 将导致db.words看起来像这样:

    { "_id" : "can", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canada", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candid", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candle", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "candy", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "cannister", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
    { "_id" : "canvas", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }

请注意,各个单词是_id的文件。这_id字段由 MongoDB 自动索引。由于索引试图保存在 RAM 中,因此我们可以采取一些技巧来加快自动完成速度并减少服务器的负载。

第 2 步:查询自动完成

对于自动补全,我们只需要单词,不需要文档的链接。 由于单词已被索引,我们使用覆盖查询 http://docs.mongodb.org/manual/core/query-optimization/#covered-query– 仅从索引回答的查询,索引通常驻留在 RAM 中。

为了坚持您的示例,我们将使用以下查询来获取自动完成的候选项:

db.words.find({_id:/^can/},{_id:1})

这给了我们结果

    { "_id" : "can" }
    { "_id" : "canada" }
    { "_id" : "candid" }
    { "_id" : "candle" }
    { "_id" : "candy" }
    { "_id" : "cannister" }
    { "_id" : "canteen" }
    { "_id" : "canvas" }

使用.explain()方法,我们可以验证该查询仅使用索引。

        {
        "cursor" : "BtreeCursor _id_",
        "isMultiKey" : false,
        "n" : 8,
        "nscannedObjects" : 0,
        "nscanned" : 8,
        "nscannedObjectsAllPlans" : 0,
        "nscannedAllPlans" : 8,
        "scanAndOrder" : false,
        "indexOnly" : true,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
            "_id" : [
                [
                    "can",
                    "cao"
                ],
                [
                    /^can/,
                    /^can/
                ]
            ]
        },
        "server" : "32a63f87666f:27017",
        "filterSet" : false
    }

请注意indexOnly:true field.

第三步:查询实际文档

尽管我们必须执行两次查询才能获取实际文档,但由于我们加快了整个过程,因此用户体验应该足够好。

步骤3.1:获取文件words收藏

当用户选择自动补全选项时,我们必须查询完整的单词文档,以便找到为自动补全选择的单词的来源文档。

db.words.find({_id:"canteen"})

这会产生这样的文档:

{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }

步骤3.2:获取实际文档

有了该文档,我们现在可以显示包含搜索结果的页面,或者像在本例中一样,重定向到您可以通过以下方式获取的实际文档:

db.yourCollection.find({_id:ObjectId("553e435f20e6afc4b8aa0efb")})

Notes

虽然这种方法一开始可能看起来很复杂(嗯,mapReduceis有点),实际上从概念上来说非常简单。基本上,您正在交易实时结果(除非您花费lotRAM)以提高速度。恕我直言,这是一笔好交易。为了使成本相当高的mapReduce阶段更加高效,实现增量mapReduce http://docs.mongodb.org/manual/tutorial/perform-incremental-map-reduce/可能是一种方法——改进我公认的被黑的mapReduce很可能是另一种方法。

最后但并非最不重要的一点是,这种方式完全是一种相当丑陋的黑客行为。您可能想深入了解 elasticsearch 或 lucene。恕我直言,这些产品非常非常适合您想要的东西。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 MongoDB 搜索实现自动完成功能 的相关文章

随机推荐

  • 是否应该迁移变更集的用户名和时间戳?

    以下文字关于OpsHub 迁移实用程序页面 https visualstudiogallery msdn microsoft com 28a90a17 d00c 4660 b7ae 42d58315ccf2表示用户名和时间戳将嵌入到迁移的变
  • 在 BIDS 中使用存储过程作为 OLE DB 源

    我正在测试 SSIS 包和存储过程 因为我只是一个初学者 我需要做的是使用在源数据库上安装的存储过程来返回数据集 然后我需要一个 ssis 包来使用存储过程返回的数据集作为 OLE DB 源来填充第二个目标表 基本上我有两张桌子 测试源 测
  • 可变参数模板的可扩展性

    我正在使用 C 11 开发一个大规模软件基础设施 该基础设施广泛使用了可变参数模板 我的问题如下 这种方法的可扩展性如何 首先 可变参数模板可以采用的参数数量是否有上限 其次 当使用许多参数时 并且 通过扩展 这些参数的许多组合将产生模板化
  • 在 Java 中用 Scala 中的 Option 包装返回 null 的方法?

    假设我有一个方法session get str String String但你不知道它会返回一个字符串还是一个 null 因为它来自 Java 在 Scala 中是否有更简单的方法来处理这个问题而不是session get foo null
  • 客户端加密的有效用例有哪些?

    我刚刚读到斯坦福大学 Javascript 加密库 http crypto stanford edu sjcl jsfiddle 示例 http jsfiddle net kRcNK 它完全用 JavaScript 支持 SHA256 AE
  • DICOM 和图像位置患者

    我试图弄清楚 DICOM 图像位置 0020 0032 是绝对坐标还是只是我拥有的任何切片方向的坐标 例如 我有两个平面 一个矢状平面和一个冠状平面 与 DICOM 标头中的 x y z 形式的相应图像位置 以毫米为单位 交错 我的问题是
  • 应用内结算,签名验证失败

    当我尝试获取订阅时 出现以下错误 签名认证失败 签名与数据不匹配 应用内结算警告 购买签名验证FAILED 不添加项目 我的代码是 String base64EncodedPublicKey MY KEY compute your publ
  • 我如何判断给定的 hWnd 是否仍然有效?

    我正在使用生成 Internet Explorer 实例的第三方类 该类有一个属性 hWnd 它返回进程的 hWnd 稍后 我可能想重用应用程序的实例 如果它仍然存在 因此我需要告诉我的帮助器类附加到它 在此之前 我想知道给定的 hWnd
  • 为什么我们需要绑定 std::cin 和 std::cout?

    默认情况下 标准输入设备与标准输出设备以以下形式绑定在一起 std cin tie std cout 这保证了在调用输入之前输出缓冲区已被刷新 所以我尝试使用来解开它们std cin tie 0 但看起来 结果 与并列的没有什么区别 inc
  • 如何使用另一个反应性对象 (ref) 的值设置反应性对象 (ref) 的值?

    我正在尝试设置 a 的值Form基于另一个反应变量中的数据 称为Product但它似乎不起作用 这Form应将其值设置为Product数据如果可用 如果不可用 则使用null 这是vue组件代码的一部分 props ProductID ty
  • CSS 动态创建列

    我有三个 div 要素 div class foo A div div class foo B div div class foo C div 期望的行为 我想编写一些CSS来随着屏幕尺寸的变化创建以下效果 不良行为 我知道如何实现以下 不
  • 如何增加数组的长度

    我有一个快速的问题 我在 java 中有一个整数数组 它的长度需要在整个类中变化 具体来说 我需要在某些情况下将其增加一 我这样尝试过 sbgHeadX new int numberOfSBG 当我需要时 我会增加整数变量 numberOf
  • JavaFX TableView scrollTo() 导致错误的行开始编辑

    我有一个可编辑的 TableView 其中有一个按钮添加新行 然后在该行的第一列上调用 table edit 当用户想要添加不在视口之外的行时 应该发生的情况是表格在视口内滚动新行并开始编辑 但是 调用 table scrollTo new
  • 在一天中的特定时间(例如,商店“营业时间”)显示 DIV

    我需要切换DIV基于时间的可见性 以便我的网站显示我的实体店何时营业 例如 07 00 15 59 显示 div 16 00 06 59 隐藏 div Thanks 这是一个基本示例 gets the current time var d
  • 如何在 MySQL 查询中使用正则表达式

    我有一个简单的任务 我需要搜索以字符串字符开头及其后一个数字的记录 我正在尝试的是这个 SELECT trecord FROM tbl WHERE trecord LIKE ALA d And SELECT trecord FROM tbl
  • 按 Enter 时未启动搜索活动

    按 Enter 时不会启动搜索活动 搜索视图在操作栏上显示得很好 但是当我输入搜索查询并按 Enter 键时 AndroidManifest xml
  • 由测试容器存储启动的 Ryuk 容器不会停止单例容器

    我有几个测试节点 但我将容器作为单例运行 ActiveProfiles test SpringBootTest webEnvironment SpringBootTest WebEnvironment RANDOM PORT AutoCon
  • 两个日期之间的周 Java + Joda 时间

    我想获取 Java 中两个日期范围之间的周数和月数 例如 开始日期 03 01 2012 结束日期 03 05 2012 由于这两个日期属于不同的两周 我希望结果为 2 而不是 0 问题的第二部分是 开始日期 02 29 2012 结束日期
  • IE可枚举垃圾创建

    我在实现 IEnumerable 接口的对象池中有以下代码 public IEnumerable
  • 使用 MongoDB 搜索实现自动完成功能

    我有一个MongoDB表格文件的收集 id 42 title candy can description canada candy canteen brand cannister candid manufacturer candle can