使用Hibernate Search Query DSL建立模糊查询时如何处理同义词和停用词

时间:2018-07-27 04:21:21

标签: java elasticsearch lucene hibernate-search

使用Hibernate Search(5.8.2.Final)将DSL查询到Elasticsearch服务器。

给出一个使用小写的标准停用词的字段分析器,然后使用以下自定义同义词:

company => co

最后是一个自定义停用词:

co

我们已经为供应商名称建立了索引:Great Spaulding Company,在Elasticsearch中,同义词和停用词分别为greatspaulding,其中包括2个术语。

我正在尝试构建查询,以使每个术语“必须”匹配,模糊或精确,具体取决于术语长度。

我得到了想要的结果,但是当其中一个词恰好是同义词或停用词并且时间长到足以使我的代码增加模糊性时,例如company~1,在这种情况下,它不再被视为同义词或停用词,并且我的查询未返回任何匹配内容,因为“ company”从未存储在首位b / c中,因此它变成了“ co”,然后被删除为停用词。

时间一些代码。似乎有些怪异,但我尝试了多种方法,并在simpleQueryString中使用withAndAsDefaultOperator// assume passed in search String of "Great Spaulding Company" String vendorName = "Great Spaulding Company"; List<String> vendorNameTerms = Arrays.asList(vendorName.split(" ")); List<String> qualifiedTerms = Lists.newArrayList(); vendorNameTerms.forEach(term -> { int editDistance = getEditDistance(term); // 1..5 = 0, 6..10 = 1, > 10 = 2 int prefixLength = getPrefixLength(term); //appears of no use with simpleQueryString String fuzzyMarker = editDistance > 0 ? "~" + editDistance : ""; qualifiedTerms.add(String.format("%s%s", term, fuzzyMarker)); }); // join my terms back together with their optional fuzziness marker String phrase = qualifiedTerms.stream().collect(Collectors.joining(" ")); bool.should( qb.simpleQueryString() .onField("vendorNames.vendorName") .withAndAsDefaultOperator() .matching(phrase) .createQuery() ); 并构建自己的短语似乎使我最接近需要的结果(但是我很乐意建议)。我正在做类似的事情:

"Great Spaulding~1"

如上所述,我发现只要不对可能的同义词或停用词添加任何模糊性,查询就会找到匹配项。因此,这些短语返回一个匹配项: "Great Spaulding~1 Co""Spaulding Co""Great Spaulding~1 Company~1"

但是由于我的代码不知道什么术语是同义词或停用词,所以它盲目地查看术语长度并说,哦,“公司”大于5个字符,我将其模糊化,将其构建不会返回匹配项的短语: "Great Company~1"Company~1

  1. 为什么Elasticsearch不将Great Spaulding Company.,作为同义词处理?
  2. 关于如何通过simpleQueryString或 另一个DSL查询?
  3. 每个人如何处理可能包含停用词的文本的模糊搜索?

[编辑]标点符号也会发生同样的问题,通常我的分析仪会删除标点符号。我无法在查询b / c的模糊搜索字符串中包含任何标点符号,ES分析器似乎并未将其视为模糊不清,并且没有得到匹配结果。

基于上述搜索字符串的示例:Great Spaulding~1 Company.,~1在我的代码中内置了短语Company,ES并没有删除标点符号或识别出同义词http://localhost:9200/myEntity/_analyze?analyzer=vendorNameAnalyzer&text=Great Spaulding Company.,

我将尝试调用ES _analyze REST api的技巧,以告诉我应该在查询中包括哪些令牌,尽管这会增加我构建的每个查询的开销。类似于great产生3个令牌:spauldingcompanyRewriteEngine On RewriteCond %{REQUEST_FILENAME}\.php -f RewriteRule ^(.*)$ $1.php [NC,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^([^/\.]+)/?$ /album.php?a_id=$1 [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^([^/\.]+)/?$ /galleries.php?g_id=$1 [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^([a-z-]+)/img/([a-z0-9-]+)/?$ img.php?g_id=$1&i_id=$2 [NC] Options -Indexes ErrorDocument 404 /404.php

1 个答案:

答案 0 :(得分:0)

  

Elasticsearch为什么不将Company〜1作为同义词处理?

我猜这是因为模糊查询是"term-level" queries,这意味着它们以确切的术语而不是分析的文本进行运算。如果您的术语经过分析后可以分解为多个标记,那么我认为为模糊查询定义可接受的行为并不容易。

有更详细的说明there(我相信它仍然适用于Elasticsearch 5.6中使用的Lucene版本)。

  

关于如何使它与simpleQueryString或另一个DSL查询一起使用的任何想法?   每个人如何处理可能包含停用词的文本的模糊搜索?

您可以尝试反转同义词:使用co => company而不是company => co,这样即使未分析“ compayn”,诸如compayn~1之类的查询也将匹配。但这当然不是令人满意的解决方案,因为其他需要分析的示例仍然无法正常工作,例如Company~1

以下是替代解决方案。

解决方案1:具有模糊性的“匹配”查询

This article描述了一种执行模糊搜索的方法,并特别说明了几种类型的模糊查询之间的区别。

不幸的是,似乎“简单查询字符串”查询中的模糊查询被转换为不执行分析的查询类型。

但是,根据您的要求,"match" query可能就足够了。为了访问Elasticsearch提供的所有设置,您将不得不使用本机查询构建:

    QueryDescriptor query = ElasticsearchQueries.fromJson(
            "{ 'query': {"
                + "'match' : {"
                    + "'vendorNames.vendorName': {"
                        // Not that using a proper JSON framework would be better here, to avoid problems with quotes in the terms
                        + "'query': '" + userProvidedTerms + "',"
                        + "'operator': 'and',"
                        + "'fuzziness': 'AUTO'"
                    + "}"
                + "}"
            + " } }"
    );
    List<?> result = session.createFullTextQuery( query ).list();

有关上述示例中“自动”含义的详细信息,请参见this page

请注意,在发布Hibernate Search 6之前,您无法将以上所示的本机查询与Hibernate Search DSL混合使用。您可以使用DSL或本机查询,但不能在同一查询中同时使用这两种查询。

解决方案2:ngrams

我认为,当查询来自您的用户并且这些用户不是Lucene专家时,最好的选择是避免完全解析查询。查询解析涉及(至少部分地)涉及文本分析,而文本分析最好留给Lucene / Elasticsearch。

然后您所要做的就是配置分析仪。

使用这些工具添加“模糊性”的一种方法是使用NGram filter。使用min_gram = 3max_gram = 3,例如:

  • 诸如“ company”之类的索引字符串将被索引为["com", "omp", "mpa", "pan", "any"]
  • 分析后,诸如“ compayn”之类的查询将被转换为(基本上是com OR omp OR mpa OR pay OR ayn
  • 这样的查询可能会匹配许多文档,但是当按分数排序时,“ Great Spaulding Company”的文档将排在最前面,因为它几乎匹配所有ngram。

在示例中,我使用了参数值min_gram = 3max_gram = 3,但是在现实世界中,像min_gram = 3max_gram = 5这样的参数会更有效,因为添加的时间更长使用ngram可以更好地搜索与索引词中较长部分匹配的词。

当然,如果您不能按分数排序,或者如果您不能在结果中接受太多的尾随部分匹配,那么该解决方案将对您不起作用。