一、前言
不同于之前的term。terms等結(jié)構(gòu)化查詢,全文搜索首先對(duì)查詢?cè)~進(jìn)行分析,然后根據(jù)查詢?cè)~的分詞結(jié)果構(gòu)建查詢。這里所說的全文指的是文本類型數(shù)據(jù)(text類型),默認(rèn)的數(shù)據(jù)形式是人類的自然語言,如對(duì)話內(nèi)容、圖書名稱、商品介紹和酒店名稱等。結(jié)構(gòu)化搜索關(guān)注的是數(shù)據(jù)是否匹配,全文搜索關(guān)注的是匹配程度;結(jié)構(gòu)化搜索一般用于精確匹配,而全文搜索用于部分匹配。本章將詳細(xì)介紹使用最多的全文搜索。
二、match查詢
match查詢是全文搜索的主要代表。對(duì)于最基本的match搜索來說,只要分詞中的一個(gè)或者多個(gè)在文檔中存在即可。例如搜索“京盛酒店”,查詢?cè)~先被分詞器切分為“京”“盛”“酒”“店”,因此,只要文檔中包含這4個(gè)字中的任何一個(gè)字,都會(huì)被搜索到。
您可能會(huì)有疑問,為什么“京盛酒店被切分為4個(gè)字而不是“京盛”“酒店”兩個(gè)詞呢?這是因?yàn)樵谀J(rèn)情況下,match查詢使用的是標(biāo)準(zhǔn)分詞器。該分詞器比較適用于英文,如果是中文則按照字進(jìn)行切分,因此默認(rèn)的分詞器不適合做中文搜索,在后面的章節(jié)中將介紹如何安裝和使用中文分詞器。
以下DSL示例為按照標(biāo)題搜索“京盛酒店”:
POST /hotel/_search
{
"query": {
"match": { //匹配title字段為"金都酒店"的文檔
"title": "京盛酒店"
}
}
}
或者按照如下形式搜索:
POST /hotel/_search
{
"query": {
"match": {
"title": {
"query": "京盛酒店"
}
}
}
}
搜索結(jié)果如下:
{
...
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.3428942,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "002",
"_score" : 1.3428942,
"_source" : {
"title" : "京盛酒店",
"city" : "北京",
"price" : "337.00",
"create_time" : "2020-07-29 13:00:00",
"amenities" : "充電停車場/可升降停車場",
"full_room" : false,
"location" : {
"lat" : 39.911543,
"lon" : 116.403
},
"praise" : 60
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "30",
"_score" : 1.2387041,
"_source" : {
"title" : "京盛酒小店",
"city" : "上海",
"price" : "300.00",
"create_time" : "2022-01-29 22:52:00",
"amenities" : "露天游泳池,普通/充電停車場",
"full_room" : false,
"praise" : 2000
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "27",
"_score" : 0.5495611,
"_source" : {
"title" : "盛況精選酒店",
"city" : "南昌",
"price" : "900.00",
"create_time" : "2022-07-29 22:50:00",
"amenities" : "露天游泳池,普通/充電停車場",
"full_room" : false,
"location" : {
"lat" : 56.918229,
"lon" : 126.422011
},
"praise" : 200
}
}
]
}
}
從結(jié)果中可以看到,匹配度最高的文檔是002,該酒店的名稱和查詢?cè)~相同,得分為1.3428942;次之的文檔是30,因?yàn)樵摼频昝Q中包含“京”“盛”“酒”“店”。但是想比前一個(gè)文檔多了一個(gè)“小”字,所以部分匹配。再次之的文檔是27,它只有“盛”“酒”“店”三個(gè)字和查詢?cè)~部分匹配,因此排在最后。
假設(shè)用戶搜索名稱中同時(shí)包含“京”和“盛”的酒店,顯然之前最后一個(gè)文檔27就不是用戶想要命中的文檔。那么在ES中,match搜索可以設(shè)置operator參數(shù),該參數(shù)決定文檔按照分詞后的詞集合進(jìn)行“與”還是“或”匹配。在默認(rèn)情況下,該參數(shù)的值為“或”關(guān)系,即operator的值為or,這也解釋了搜索結(jié)果中包含部分匹配的文檔。如果希望各個(gè)詞之間的匹配結(jié)果是“與”關(guān)系,則可以設(shè)置operator參數(shù)的值為and。
下面的請(qǐng)求示例設(shè)置查詢?cè)~之間的匹配結(jié)果為“與”關(guān)系:
POST /hotel/_search
{
"query": {
"match": {
"title": {
"query": "京盛酒店",
"operator": "and"
}
}
}
}
搜索結(jié)果如下:
{
...
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.3428942,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "002",
"_score" : 1.3428942,
"_source" : {
"title" : "京盛酒店",
"city" : "北京",
"price" : "337.00",
"create_time" : "2020-07-29 13:00:00",
"amenities" : "充電停車場/可升降停車場",
"full_room" : false,
"location" : {
"lat" : 39.911543,
"lon" : 116.403
},
"praise" : 60
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "30",
"_score" : 1.2387041,
"_source" : {
"title" : "京盛酒小店",
"city" : "上海",
"price" : "300.00",
"create_time" : "2022-01-29 22:52:00",
"amenities" : "露天游泳池,普通/充電停車場",
"full_room" : false,
"praise" : 2000
}
}
]
}
}
有時(shí)搜索多個(gè)關(guān)鍵字,關(guān)鍵詞和文檔在某一個(gè)比例上匹配即可,如果使用“與”操作過于嚴(yán)苛,如果使用“或”操作又過于寬松。這時(shí)可以采用minimum_should_match參數(shù),該參數(shù)叫作最小匹配參數(shù),其值為一個(gè)數(shù)值,意義為可以匹配上的詞的個(gè)數(shù).在一般情況下將其設(shè)置為一個(gè)百分?jǐn)?shù),因?yàn)樵谡鎸?shí)場景中并不能精確控制具體的匹配數(shù)量。以下示例設(shè)置最小匹配為80%的文檔:
POST /hotel/_search
{
"query": {
"match": {
"title": {
"query": "京盛酒店",
"operator": "or",
"minimum_should_match": "80%" //設(shè)置最小匹配度為80%
}
}
}
}
這樣的話就需要滿足最后命中的文檔字?jǐn)?shù)占查詢條件中“京盛酒店”的80%(向下取整),例如這里4*80%,其實(shí)查詢結(jié)果只需要有條件中任意三個(gè)字符即可。
在Java客戶端上可以使用QueryBuilders.matchQuery()方法構(gòu)建match請(qǐng)求,分別給該方法傳入字段名稱和查詢值即可進(jìn)行match查詢。以下代碼展示了match請(qǐng)求的使用邏輯:
service層:
public List<Hotel> matchQuery(HotelDocRequest hotelDocRequest) throws IOException {
//新建搜索請(qǐng)求
String indexName = getNotNullIndexName(hotelDocRequest);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//查詢title且查詢值之間關(guān)系是or,并且最小匹配參數(shù)為80%
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("title", hotelDocRequest.getTitle()).operator(Operator.OR).minimumShouldMatch("80%");
searchSourceBuilder.query(matchQueryBuilder);
searchRequest.source(searchSourceBuilder);
return getQueryResult(searchRequest);
}
controller層:
@PostMapping("/query/match")
public FoundationResponse<List<Hotel>> matchQuery(@RequestBody HotelDocRequest hotelDocRequest) {
try {
List<Hotel> hotelList = esQueryService.matchQuery(hotelDocRequest);
if (CollUtil.isNotEmpty(hotelList)) {
return FoundationResponse.success(hotelList);
} else {
return FoundationResponse.error(100,"no data");
}
} catch (IOException e) {
log.warn("搜索發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
} catch (Exception e) {
log.error("服務(wù)發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
}
}
postman調(diào)用截圖:
三、multi_match查詢
有時(shí)用戶需要在多個(gè)字段中查詢關(guān)鍵詞,除了使用布爾查詢封裝多個(gè)match查詢之外,可替代的方案是使用multi_match。可以在multi_match的query子句中組織數(shù)據(jù)匹配規(guī)則,并在fields子句中指定需要搜索的字段列表。
下面的示例在title和amenities兩個(gè)字段中同時(shí)搜索“假日”關(guān)鍵詞:
POST /hotel/_search
{
"query": {
"multi_match": {
"query": "假日",
"fields": [
"amenities",
"title"
]
}
}
}
搜索結(jié)果如下:
{
"took" : 14,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 4.2939954,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "28",
"_score" : 4.2939954,
"_source" : {
"title" : "京盛假日酒店",
"city" : "上海",
"price" : "600.00",
"create_time" : "2021-04-29 22:52:00",
"amenities" : "露天游泳池,普通/充電停車場",
"full_room" : false,
"praise" : 200
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "003",
"_score" : 1.9696801,
"_source" : {
"title" : "文雅文化酒店",
"city" : "天津",
"price" : "260.00",
"create_time" : "2021-02-27 22:00:00",
"amenities" : "提供假日party,免費(fèi)早餐,浴池,充電停車場",
"full_room" : true,
"location" : {
"lat" : 39.186555,
"lon" : 117.162767
},
"praise" : 30
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "29",
"_score" : 1.9163029,
"_source" : {
"title" : "京盛欣欣酒店",
"city" : "上海",
"price" : "700.00",
"create_time" : "2022-01-29 22:52:00",
"amenities" : "提供假日party,露天游泳池,普通/充電停車場",
"full_room" : false,
"praise" : 200
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "004",
"_score" : 1.6876338,
"_source" : {
"title" : "京盛集團(tuán)酒店",
"city" : "上海",
"price" : "800.00",
"create_time" : "2021-05-29 21:35:00",
"amenities" : "浴池(假日需預(yù)訂),室內(nèi)游泳池,普通停車場/充電停車場",
"full_room" : true,
"location" : {
"lat" : 36.940243,
"lon" : 120.394
},
"praise" : 100
}
}
]
}
}
根據(jù)結(jié)果可以看到,命中的文檔要么在title中包含“假日”關(guān)鍵詞,要么在amenities字段中包含“假日”關(guān)鍵詞。
且之前在Match搜索講到的operator,minimum_should_match等參數(shù)在multi_match搜索中同樣適用。
在Java客戶端上可以使用QueryBuilders.multiMatchQuery()方法或者直接new MultiMatchQueryBuilder()構(gòu)建multi_match請(qǐng)求
可以看到,我們構(gòu)造MultiMatchQueryBuilder,除了查詢值,字段它接收的是一個(gè)可變長String數(shù)組:
所以我們可以在傳參hotelDocRequest加兩個(gè)參數(shù),一個(gè)是multiQueryValue代表要查詢的值,另一個(gè)是multiQueryPropertyNames代表想要在哪些字段查詢
分別給該方法傳入查詢值和多個(gè)字段名稱即可進(jìn)行multi_match查詢。以下代碼展示了multi_match請(qǐng)求的使用邏輯:
Service層
由于上面講到構(gòu)造MultiMatchQueryBuilder接收的是可變長String數(shù)組,所以我們要對(duì)傳參的List通過list.stream().toArray(String[]::new);轉(zhuǎn)化為String可變長數(shù)組(String…等價(jià)于String[])。
public List<Hotel> multiMatchQuery(HotelDocRequest hotelDocRequest) throws IOException {
//新建搜索請(qǐng)求
String indexName = getNotNullIndexName(hotelDocRequest);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MultiMatchQueryBuilder multiMatchQueryBuilder = new MultiMatchQueryBuilder(hotelDocRequest.getMultiQueryValue(), hotelDocRequest.getMultiQueryPropertyNames().toArray(new String[0]));
searchSourceBuilder.query(multiMatchQueryBuilder);
searchRequest.source(searchSourceBuilder);
return getQueryResult(searchRequest);
}
controller層:
@PostMapping("/query/multiMatch")
public FoundationResponse<List<Hotel>> multiMatchQuery(@RequestBody HotelDocRequest hotelDocRequest) {
try {
List<Hotel> hotelList = esQueryService.multiMatchQuery(hotelDocRequest);
if (CollUtil.isNotEmpty(hotelList)) {
return FoundationResponse.success(hotelList);
} else {
return FoundationResponse.error(100,"no data");
}
} catch (IOException e) {
log.warn("搜索發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
} catch (Exception e) {
log.error("服務(wù)發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
}
}
postman運(yùn)行截圖:
四、match_phrase查詢
match_phrase用于匹配短語,與match查詢不同的是,match_phrase用于搜索確切的短語或臨近的詞語。假設(shè)在酒店標(biāo)題中搜索“京盛酒店”,希望酒店標(biāo)題中的“京盛酒店”四字完全按照搜索詞的順序并且緊鄰,此時(shí)就需要使用match_phrase查詢:
POST /hotel/_search
{
"query": {
"match_phrase": {
"title": {
"query": "京盛酒店"
}
}
}
}
結(jié)果如下:
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.3428942,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "002",
"_score" : 1.3428942,
"_source" : {
"title" : "京盛酒店",
"city" : "北京",
"price" : "337.00",
"create_time" : "2020-07-29 13:00:00",
"amenities" : "充電停車場/可升降停車場",
"full_room" : false,
"location" : {
"lat" : 39.911543,
"lon" : 116.403
},
"praise" : 60
}
}
]
}
}
根據(jù)上述結(jié)果可知,使用match_phrase查詢后,只有文檔002命中,而類似之前的“京盛集團(tuán)酒店”等類似文檔沒有被命中,這是為什么呢?
我們知道,在默認(rèn)標(biāo)準(zhǔn)分詞器的情況下,文檔002的title字段被切分為“京”“盛”“酒”“店”,其中這些分詞后的文檔下標(biāo)“京”代表0,盛”代表1,“酒”代表2,“店”代表3,而對(duì)于match_phrase查詢,在不去設(shè)置下標(biāo)移動(dòng)步長的情況下這些分詞文檔想要移動(dòng)到理想位置(查詢?cè)~的位置,這里就是京盛酒店)的步數(shù)默認(rèn)就是0,而可以發(fā)現(xiàn),我們命中的文檔002“京盛酒店”,這個(gè)文檔下標(biāo)其實(shí)就已經(jīng)是理想位置了,不需要額外移動(dòng),相當(dāng)于步長就是0,所以能夠命中。而對(duì)于“京盛集團(tuán)酒店”,分詞后“盛”想要移動(dòng)到“酒”這個(gè)下標(biāo),需要移動(dòng)2次,所以步長是2,不符合默認(rèn)的步長,所以無法命中。
那么如果需要“京盛集團(tuán)酒店”也能夠被命中,則可以設(shè)置match_phrase查詢的slop參數(shù),它用來調(diào)節(jié)匹配詞之間的距離閾值,即上面說的步長,下面的DSL將slop設(shè)置為2
POST /hotel/_search
{
"query": {
"match_phrase": {
"title": {
"query": "京盛酒店",
"slop":2
}
}
}
}
可以看到這樣就能命中“京盛集團(tuán)酒店”了
在Java客戶端上可以使用QueryBuilders.matchPhraseQuery()方法構(gòu)建match_phrase請(qǐng)求,分別給該方法傳入查詢字段和值即可運(yùn)行multi_match查詢。這一點(diǎn)和match搜索很像。以下代碼展示了match_phrase請(qǐng)求的使用邏輯:
Service層:
public List<Hotel> matchPhraseQuery(HotelDocRequest hotelDocRequest) throws IOException {
//新建搜索請(qǐng)求
String indexName = getNotNullIndexName(hotelDocRequest);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//構(gòu)造MatchPhraseQueryBuilder且設(shè)置步長為2
MatchPhraseQueryBuilder matchPhraseQueryBuilder = new MatchPhraseQueryBuilder("title", hotelDocRequest.getTitle()).slop(2);
searchSourceBuilder.query(matchPhraseQueryBuilder);
searchRequest.source(searchSourceBuilder);
return getQueryResult(searchRequest);
}
Controller層:文章來源:http://www.zghlxwxcb.cn/news/detail-667099.html
@PostMapping("/query/matchPhrase")
public FoundationResponse<List<Hotel>> matchPhraseQuery(@RequestBody HotelDocRequest hotelDocRequest) {
try {
List<Hotel> hotelList = esQueryService.matchPhraseQuery(hotelDocRequest);
if (CollUtil.isNotEmpty(hotelList)) {
return FoundationResponse.success(hotelList);
} else {
return FoundationResponse.error(100,"no data");
}
} catch (IOException e) {
log.warn("搜索發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
} catch (Exception e) {
log.error("服務(wù)發(fā)生異常,原因?yàn)?{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
}
}
Postman運(yùn)行截圖:文章來源地址http://www.zghlxwxcb.cn/news/detail-667099.html
到了這里,關(guān)于Elasticsearch(十四)搜索---搜索匹配功能⑤--全文搜索的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!