如何在hibernate搜索中使用多个查询字符串检索精确的搜索结果



我们使用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);

最新更新