如何在数据库中搜索文本片段

Dan*_*ler 6 mysql database indexing search full-text-search

是否有任何可用的开源或商业工具允许对数据库内容进行文本片段索引并可以从Java查询?

问题的背景是一个包含数十万条记录的大型MySQL数据库表,其中包含几个VARCHAR列.在这些列中,人们希望搜索内容的片段,因此全文索引(基于单词边界)将无济于事.

编辑:[补充说明为什么这些第一个建议不能解决问题:]

这就是为什么MySQL的内置全文索引不能完成这项工作,Lucene或Sphinx也都不会在答案中提出.我已经看过这两个,但据我所知,这些是基于索引,不包括停用词和为真正的全文搜索做各种明智的事情.然而这不合适,因为我可能正在寻找像"oison"这样的搜索词,它必须与"Roisonic Street"以及"Poison-Ivy"相匹配.这里的关键区别在于搜索项只是列内容的一个片段,不需要由任何特殊字符或空格分隔.

EDIT2:[增加了一些背景信息:]基于此要求实现的请求功能是对商品管理系统中的项目描述进行非常松散的搜索.用户通常不知道正确的项目编号,而只知道项目名称的一部分.遗憾的是,这些描述的质量相当低,它们来自遗留系统,无法轻易更改.例如,如果人们正在寻找大锤,他们就会进入"雪橇".使用基于单词/令牌的索引,这将找不到存储为"大锤"的匹配,但只有那些听"大锤".需要涵盖各种奇怪的差异,使基于令牌的方法变得不切实际.

目前我们唯一能做的就是LIKE '%searchterm%'查询,有效地禁用任何索引使用并需要大量资源和时间.

理想情况下,任何此类工具都会创建一个索引,使我能够非常快速地获得类似查询的结果,这样我就可以实现类似聚光灯的搜索,只有当用户选择时,才能通过主键从MySQL表中检索"真实"数据结果记录.

如果可能,索引应该是可更新的(无需完全重建),因为数据可能会更改,并且应该可供其他客户端立即搜索.

我很乐意获得建议和/或经验报告.

编辑3:商业解决方案发现"正常工作" 虽然我对这个问题有很多好的答案,但我想在这里注意,最后我们选择了一种名为"QuickFind"的商业产品,由德国人制造和销售公司名为"HMB Datentechnik".请注意,我以任何方式与他们联系,因为当我继续描述他们的产品可以做什么时,它可能看起来像.不幸的是,他们的网站看起来很糟糕,只有德语,但产品本身真的很棒.我目前有一个试用版本 - 你必须联系他们,没有下载 - 我印象非常深刻.

由于网上没有全面的文档,我将尝试描述我迄今为止的经验.

他们所做的是根据数据库内容构建自定义索引文件.他们可以通过ODBC集成,但据我所知,客户很少这样做.相反 - 这就是我们可能会做的 - 您从主数据库生成文本导出(如CSV)并将其提供给索引器.这使您可以完全独立于实际的表结构(或任何SQL数据库); 事实上,我们从几个表中导出数据连接在一起.索引可以在以后随时增量更新.

基于它们的服务器(仅仅250kb左右,作为控制台应用程序或Windows服务运行)服务侦听TCP端口上的查询.该协议是基于文本的,看起来有点"旧",但它很简单,有效.基本上,您只需传递要查询的可用索引和搜索条件(片段),空格分隔.有三种输出格式,HTML/JavaScript数组,XML或CSV.目前我正在为一个有点"过时"的有线协议的Java包装器工作.但结果是太棒了:我现在有一个样本数据进行索引8列设置的约500.000记录和我的测试应用程序触发所有8列一个JTextField的内容的搜索上的每一个按键同时被编辑和可更新的结果显示(JTable)实时!这种情况发生时无需访问最初来自MySQL数据的MySQL实例.根据您返回的列,您可以通过使用该行的主键查询MySQL来询问"原始"记录(当然,需要包含在QuickFind索引中).

索引大约是文本导出版本数据大小的30-40%.索引主要受磁盘I/O速度的限制; 我的500.000条记录需要大约一两分钟才能处理完毕.

很难描述这一点,因为当我看到内部产品演示时,我甚至很难相信.他们提供了一个1000万行地址数据库,并搜索了名称,地址和电话号码的片段,当点击"搜索"按钮时,结果在一秒钟之内回来 - 所有这些都在笔记本上完成!据我所知,他们经常与SAP或CRM系统集成,以改善呼叫中心代理只需了解呼叫者姓名或地址片段的搜索时间.

所以无论如何,我在描述这个时可能不会好多了.如果你需要这样的东西,你一定要去看看.谷歌翻译在将他们的网站从德语翻译成英语方面做得相当不错,所以这可能是一个好的开始.

Jas*_*rue 10

这可能不是你想听到的,因为我认为你试图用SQL代码来解决这个问题,但Lucene将是我的第一选择.您还可以使用其他工具构建相当聪明的排名和提升技术.Lucene是用Java编写的,因此它应该为您提供所需的接口.

如果您是Microsoft商店,那么您所寻找的大部分内容都内置在SQL Server中,并且可以启用通配符,这样您就可以进行部分单词匹配.

在Lucene和Lucene.Net中,如果您愿意,可以使用通配符匹配.但是,不支持将通配符用作搜索中的第一个符号.如果你想要能够使用第一个字符通配符,你可能需要自己实现某种基于trie的索引,因为在很多情况下,将一组术语过滤到合理的类型是一种昂贵的操作全文搜索应用程序最常需要的索引,其中后缀词干通常更有价值.

您可以通过将setAllowLeadingWildcard设置为true显然更改Lucene中的Query Parser实例来覆盖此规则.

我很确定两端的通配符搜索本身就是低效的.跳过列表有时用于提高使用明文进行此类搜索的性能,但我认为您更有可能在类似grep的实现中找到类似于广义文本索引工具的实现.

您可以使用其他解决方案来描述一个单词拼写为两个单词的位置,反之亦然.例如,Lucene支持模糊查询.正交和形态变体可以通过提供过滤器来处理,该过滤器基于某种贝叶斯机制提供建议,或者通过索引技巧,即采用频繁变体的语料库并用这些术语填充索引.我甚至从结构化数据中看到了填充到全文引擎中的知识(例如,将酒店表中的城市名称和"酒店"一词添加到记录中,使"巴黎酒店"更有可能包含养老金记录-house CaissedesDépôts.)虽然不是一个微不足道的问题,但它可以在不破坏基于单词的搜索的优势的情况下进行管理.


Ben*_*Cox 4

我自己没有这个具体要求,但我的经验告诉我 Lucene 可以做到这一点,尽管可能不是独立的。我肯定会通过 Solr 使用它,正如 Michael Della Bitta 在第一个答案中所描述的那样。他给出的链接非常准确 - 阅读它以获取更多背景信息。

简而言之,Solr 允许您定义自定义 FieldType。它们由索引时间分析器和查询时间分析器组成。分析器确定如何处理文本,每个分析器都由一个 Tokenizer 和零到多个 TokenFilter 组成。Tokenizer 将文本分割成块,然后每个 TokenFilter 可以添加、减去或修改标记。

因此,该字段最终可以索引与原始文本完全不同的内容,如果需要,还可以包括多个标记。因此,您想要的是原始文本的多令牌副本,您可以通过向 Lucene 发送类似“my_ngram_field:sledge”的内容来查询该副本。不涉及通配符:-)

然后,您遵循类似于 solrconfig.xml 文件中提供的前缀搜索的模型:

<fieldType name="prefix_token" class="solr.TextField" positionIncrementGap="1">
    <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="20"/>
    </analyzer>
    <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory" />
    </analyzer>
</fieldType>
Run Code Online (Sandbox Code Playgroud)

EdgeNGramFilterFactory 是他们实现搜索框自动完成前缀匹配的方式。它获取来自前一阶段的标记(将单个空格分隔的单词转换为小写)并将它们扇出到前沿的每个子字符串中。大锤 = s、sl、sle、雪橇、sledg、sledge、sledgeh 等。

您需要遵循此模式,但将 EdgeNGramFilterFactory 替换为您自己的,它会执行该字段中的所有 NGram。默认的 org.apache.solr.analysis.NGramFilterFactory 是一个好的开始,但它会进行字母换位以进行拼写检查。您可以复制它并将其删除 - 这是一个实现起来非常简单的类。

使用自己的 MyNGramFilterFactory 拥有自己的 FieldType(称为 ngram_text)后,只需创建原始字段和 ngram 字段,如下所示:

    <field name="title" type="text" indexed="true" stored="true"/>
    <field name="title_ngrams" type="ngram_text" indexed="true" stored="false"/>
Run Code Online (Sandbox Code Playgroud)

然后告诉它将原始字段复制到新字段中:

<copyField source="title" dest="title_ngrams"/>
Run Code Online (Sandbox Code Playgroud)

好吧,现在当您搜索“title_ngrams:sledge”时,您应该会得到包含此内容的文档列表。然后,在查询的字段列表中,您只需告诉它检索名为 title 的字段,而不是字段 title_ngrams。

这应该足以让您将事物组合在一起并轻松地将其调整到令人惊讶的性能水平。在以前的工作中,我们拥有一个数据库,其中包含超过一千万个具有大量 HTML 描述的产品,并设法让 Lucene 在处理数十个并发查询的中型服务器上在 200 毫秒内完成标准查询和拼写检查。当您有很多用户时,缓存就会发挥作用并让人尖叫!

哦,增量(虽然不是实时)索引是小菜一碟。它甚至可以在高负载下执行此操作,因为它会在后台创建和优化新索引,并在换入之前对其进行自动预热。非常灵活。

祝你好运!