使用 Hibernate Search (5.8.2.Final) 查询 DSL 到 Elasticsearch 服务器。
给定一个执行小写、标准停用词的字段分析器,然后是一个自定义同义词:
company => co
最后,自定义停用词:
co
我们已经索引了一个供应商名称:
Great Spaulding Company
,在同义词和停用词之后归结为 Elasticsearch 中的 2 个术语:great
和 spaulding
.我正在尝试构建我的查询,以便每个术语“必须”匹配,模糊或精确,具体取决于术语长度。
我得到了我想要的结果,除非其中一个术语恰好是同义词或停用词并且足够长以至于我的代码添加了模糊性,例如
company~1
,在这种情况下,它不再被视为同义词或停用词,并且我的查询返回不匹配,因为“公司”从未存储在第一位 b/c 它变成“co”,然后作为停用词删除.是时候写一些代码了。它可能看起来有点 hacky,但我尝试了很多方法并使用
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"
但是由于我的代码不知道哪些术语是同义词或停用词,它会盲目地查看术语长度并说,哦,“公司”大于 5 个字符,我会使其模糊,它会构建这类短语不返回匹配项:
"Great Spaulding~1 Company~1"
或 "Great Company~1"
Company~1
作为同义词? 另一个 DSL 查询?
[编辑] 我的分析仪通常会删除的标点符号也会出现同样的问题。我不能在查询 b/c 中的模糊搜索字符串中包含任何标点符号,ES 分析器似乎没有将其视为非模糊的,并且我没有得到匹配结果。
基于上述搜索字符串的示例:
Great Spaulding Company.,
在我的代码中内置到短语 Great Spaulding~1 Company.,~1
并且 ES 不会删除标点符号或识别同义词 Company
我将尝试调用 ES _analyze REST api 的技巧,以便它告诉我应该在查询中包含哪些 token ,尽管这会增加我构建的每个查询的开销。类似于
http://localhost:9200/myEntity/_analyze?analyzer=vendorNameAnalyzer&text=Great Spaulding Company.,
产生 3 个 token :great
, spaulding
和 company
.
最佳答案
Why is Elasticsearch not processing Company~1 as a synonym?
我猜是因为模糊查询是 "term-level" queries ,这意味着它们以精确的术语而不是分析的文本进行操作。如果您的术语在分析后解析为多个标记,我认为为模糊查询定义可接受的行为并不容易。
有更详细的解释there (我相信它仍然适用于 Elasticsearch 5.6 中使用的 Lucene 版本)。
Any idea on how I can make this work with simpleQueryString or another DSL query? How is everyone handling fuzzy searching on text that may contain stopwords?
您可以尝试颠倒您的同义词:使用
co => company
而不是 company => co
,以便查询如 compayn~1
即使“compayn”没有被分析,也会匹配。但这当然不是一个令人满意的解决方案,因为其他需要分析的示例仍然不起作用,例如 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有关上述示例中“AUTO”含义的详细信息。
请注意,在 Hibernate Search 6 发布之前,您不能将如上所示的 native 查询与 Hibernate Search DSL 混合使用。您要么使用 DSL,要么使用 native 查询,但不能在同一个查询中同时使用这两种查询。
解决方案 2:ngram
在我看来,当查询来自您的用户并且这些用户不是 Lucene 专家时,您最好的选择是避免完全解析查询。查询解析涉及(至少部分)文本分析,文本分析最好留给 Lucene/Elasticsearch。
然后你所能做的就是配置分析器。
使用这些工具添加“模糊性”的一种方法是使用 NGram filter .与
min_gram = 3
和 max_gram = 3
, 例如:["com", "omp", "mpa", "pan", "any"]
com OR omp OR mpa OR pay OR ayn
我使用了参数值
min_gram = 3
和 max_gram = 3
例如,但在现实世界的应用程序中类似于 min_gram = 3
和 max_gram = 5
会更好,因为添加的更长的 ngram 会给与索引词的较长部分匹配的搜索词提供更好的分数。当然,如果你不能按分数排序,或者如果你不能在结果中接受太多的尾随部分匹配,那么这个解决方案将不适合你。
关于java - 使用 Hibernate Search Query DSL 构建模糊查询时如何处理同义词和停用词,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51550725/