我们使用hibernate搜索表单5.9.2,并希望获得准确的搜索结果,如:
如果用户开始使用
John -> all data with John should display
John Murphy -> all data with John murphy should display
John murphy Columbia -> Only data with John murphy Columbia should display
John murphy Columbia SC -> Only data with John murphy Columbia should display
John murphy Columbia SC 29201 -> Only data with John murphy Columbia SC 29201
29201 -> Only data with 29201 as zipcode should be displayed.
and so on...
基本上,我们试图从索引上的多个字段中搜索准确的记录。
我们在Name、Address1、address2、city、zipcode、state等字段中有包含这些数据的实体。
我们已经尝试过bool()
(带有应该/必须)查询,但由于我们不确定用户将首先输入什么数据,它可能是邮政编码、州、城市——文本搜索中的任何位置。
请分享您关于分析器/策略的知识/逻辑,我们可以使用hibernate search/locene来实现这一点。
以下是索引结构:
> {
> "_index" : "client_master_index_0300",
> "_type" : "com.csc.pt.svc.data.to.Basclt0300TO",
> "_id" : "518,1",
> "_score" : 4.0615783,
> "_source" : {
> "id" : "518,1",
> "cltseqnum" : 518,
> "addrseqnum" : "1",
> "addrln1" : "Dba",
> "addrln2" : "Betsy Evans",
> "city" : "SDA",
> "state" : "SC",
> "zipcode" : "89756-4531",
> "country" : "USA",
> "basclt0100to" : {
> "cltseqnum" : 518,
> "clientname" : "Betsy Evans",
> "longname" : "Betsy Evans",
> "id" : "518"
> },
> "basclt0900to" : {
> "cltseqnum" : 518,
> "id" : "518"
> }
> }
> }
以下是输入
Akash Agrawal 29021
响应包含匹配akash、agrwal、29,2、1、01等的所有记录…
我们试图实现的是准确的搜索结果,对于上述搜索输入,结果应该只包含Akash Agrawal 29201的数据,而不是其他数据。
我们基本上是在basclt0100to.longname,addrln1,addrln2,城市,州,邮政编码,国家/地区上搜索。
索引定义低于
> {
> "client_master_index_0300" : {
> "aliases" : { },
> "mappings" : {
> "com.csc.pt.svc.data.to.Basclt0300TO" : {
> "dynamic" : "strict",
> "properties" : {
> "addrln1" : {
> "type" : "text",
> "store" : true
> },
> "addrln2" : {
> "type" : "text",
> "store" : true
> },
> "addrln3" : {
> "type" : "text",
> "store" : true
> },
> "addrseqnum" : {
> "type" : "text",
> "store" : true
> },
> "basclt0100to" : {
> "properties" : {
> "clientname" : {
> "type" : "text",
> "store" : true
> },
> "cltseqnum" : {
> "type" : "long",
> "store" : true
> },
> "firstname" : {
> "type" : "text",
> "store" : true
> },
> "id" : {
> "type" : "keyword",
> "store" : true,
> "norms" : true
> },
> "longname" : {
> "type" : "text",
> "store" : true
> },
> "midname" : {
> "type" : "text",
> "store" : true
> }
> }
> },
> "basclt0900to" : {
> "properties" : {
> "cltseqnum" : {
> "type" : "long",
> "store" : true
> },
> "email1" : {
> "type" : "text",
> "store" : true
> },
> "id" : {
> "type" : "keyword",
> "store" : true,
> "norms" : true
> }
> }
> },
> "city" : {
> "type" : "text",
> "store" : true
> },
> "cltseqnum" : {
> "type" : "long",
> "store" : true
> },
> "country" : {
> "type" : "text",
> "store" : true
> },
> "id" : {
> "type" : "keyword",
> "store" : true
> },
> "state" : {
> "type" : "text",
> "store" : true
> },
> "zipcode" : {
> "type" : "text",
> "store" : true
> }
> }
> }
> },
> "settings" : {
> "index" : {
> "creation_date" : "1535607176216",
> "number_of_shards" : "5",
> "number_of_replicas" : "1",
> "uuid" : "x4R71LNCTBSyO9Taf8siOw",
> "version" : {
> "created" : "6030299"
> },
> "provided_name" : "client_master_index_0300"
> }
> }
> }
> }
到目前为止,我已经尝试使用edgingraanalyzer,lucene查询的标准分析器。我尝试过布尔()查询、关键字查询、短语,尝试过文档中提供的所有功能。
但我确信我错过了我们应该使用的策略/逻辑。
以下是我正在使用的当前查询,并给出了所附的快照结果
Query finalQuery = queryBuilder.simpleQueryString()
.onFields("basclt0100to.longname", "addrln1", "addrln2"
,"city","state","zipcode", "country")
.withAndAsDefaultOperator()
.matching(lowerCasedSearchTerm)
.createQuery();
FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(finalQuery, Basclt0300TO.class);
fullTextQuery.setMaxResults(this.data.getPageSize()).setFirstResult(this.data.getPageSize());
List<String> projectedFields = new ArrayList<String>();
for (String fieldName : projections)
projectedFields.add(fieldName);
@SuppressWarnings("unchecked")
List<Cltj001ElasticSearchResponseTO> results = fullTextQuery.
setProjection(projectedFields.toArray(new String[projectedFields.size()]))
.setResultTransformer( new BasicTransformerAdapter() {
@Override
public Cltj001ElasticSearchResponseTO transformTuple(Object[] tuple, String[] aliases) {
return new Cltj001ElasticSearchResponseTO((String) tuple[0], (long) tuple[1],
(String) tuple[2], (String) tuple[3], (String) tuple[4],
(String) tuple[5],(String) tuple[6], (String) tuple[7], (String) tuple[8]);
}
})
.getResultList();
resultsClt0300MasterIndexList = results;
搜索到:阿卡什29201&搜索到:akash 1主
在这里,您可以看到,我们有包含akash,sh,29229201的所有数据。
预期结果:
阿卡什·阿格拉瓦尔-29201Akash Agrawal-SC大街1号,邮编29201
基本上只有包含/匹配输入字符串的精确数据。
使用的分析仪:索引时间
@AnalyzerDef(name = "autocompleteEdgeAnalyzer",
//Split input into tokens according to tokenizer
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(
factory = EdgeNGramFilterFactory.class, // Generate prefix tokens
params = {
@Parameter(name = "minGramSize", value = "3"),
@Parameter(name = "maxGramSize", value = "3")
}
)
})
查询时间覆盖:
@AnalyzerDef(name = "withoutEdgeAnalyzerFactory",
// Split input into tokens according to tokenizer
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
}
/*filters = {
// Normalize token text to lowercase, as the user is unlikely to
// care about casing when searching for matches
@TokenFilterDef(factory = PatternReplaceFilterFactory.class, params = {
@Parameter(name = "pattern", value = "([^a-zA-Z0-9\.])"),
@Parameter(name = "replacement", value = " "),
@Parameter(name = "replace", value = "all") }),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class) }*/)
希望这些细节能有所帮助。
最简单的解决方案是在布尔查询中使用Occur.MUST
而不是Occur.SHOULD
,只需要更改一点点代码。然后,您将只获得每个关键字都匹配的文档,而不是当前至少匹配一个关键字的文档。
然而,这并不是最正确的解决方案。尝试一下,然后看看下面,如果你想了解发生了什么。
首先,您不需要自己拆分输入字符串;这就是Lucene(以及Elasticsearch)的工作,也就是所谓的"文本分析"。在开始使用Hibernate Search之前,您确实应该了解文本分析。
简而言之,文本分析是将单个字符串转换为可用于全文索引的"标记"(单词)的过程。
文本分析在两种情况下进行(我在简化,但或多或少就是这样):
- 在索引文档时,会分析每个字段的内容,Elasticsearch会将分析结果(令牌列表)存储在索引中
- 查询时,将分析查询字符串,全文引擎将在索引中查找每个生成的令牌
文本分析包括三个步骤:
- 字符过滤,我不会详细描述,因为它通常被跳过,所以你可能不需要它
- 标记化,将单个字符串拆分为多个部分,称为"标记"。简而言之,它从字符串中提取单词
- 令牌过滤,它对令牌应用转换,例如将它们变成小写,用更简单的等价物替换变音符号("é"=>"e","à"=>"a",…),将令牌进一步拆分为ngrams("word"=>["w","wo","wor","word"]),等等
此文本分析的目的是允许比"相等字符串"更微妙的匹配。特别是,它可以在文档中查找单词,执行不区分大小写的搜索,但也可以进行更微妙的搜索,如"以给定字符串开头的单词"(使用EdgeNgramTokenFilter
)或"wifi"one_answers"wi-fi"等看似无关的单词的匹配。
搜索的行为完全取决于您在索引时和查询时应用的分析器。通常,在索引时和查询时应用相同的分析,但在一些非常特定的高级用例中(例如使用EdgeNGramTokenFilter
时),在查询时需要使用稍微不同的分析器。
正如您所看到的,Lucene/Elasticsearch已经完成了您想要的操作,即将输入字符串拆分为多个"单词"。此外,如果您使用正确的分析器,您就不需要自己将输入字符串小写,因为令牌过滤器会处理这一问题。
以上都是基础知识,在开始使用Hibernate Search之前,您确实需要了解这些知识。
现在具体说明一下:问题是,当您只使用.keyword()
查询时,字符串确实会被拆分为多个单词,但Hibernate Search会搜索与这些单词中的任何匹配的文档。这不是你想要的:你想搜索与所有这些单词匹配的文档。
为了做到这一点,我建议您使用"简单查询字符串"查询。您将创建它,或多或少类似于keyword()
查询,但它有一些不错的附加功能,使其更适合于您正在构建的web界面。特别是,它允许您通过将默认运算符设置为"and"来要求查询中的所有"words"匹配。
例如:
Query finalQuery = queryBuilder.simpleQueryString()
.onFields("basclt0100to.longname", "addrln1", "addrln2"
,"city","state","zipcode", "country")
.withAndAsDefaultOperator()
.matching(searchTerms)
.createQuery();
FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(finalQuery, Basclt0300TO.class);