3、ElasticSearch搜索結果處理 3.1、排序 Elasticsearch預設是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序的欄位類型有如下幾種 keyword類型 數值類型 地理坐標類型 日期類型 ... 3.1.1、普通欄位排序 keyword、數 ...
3、ElasticSearch搜索結果處理
3.1、排序
- Elasticsearch預設是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序的欄位類型有如下幾種
- keyword類型
- 數值類型
- 地理坐標類型
- 日期類型
- ...
3.1.1、普通欄位排序
-
keyword、數值、日期類型排序的語法基本一致;DSL語法如下所示
-
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "FIELD": "desc" // 排序欄位、排序方式ASC、DESC } ] }
-
-
排序條件是一個數組,也就是可以寫多個排序條件;按照聲明的排序,當第一個條件相等的時候,再按照第二個條件排序,依次類推
-
示例
-
酒店數據按照用戶評價(score)降序排序,評價相同的按照價格(price)升序排序
-
DSL語句如下所示(
match
處是自定義的,也可以直接使用match_all
)-
GET hotel/_search { "query": { "match": { "name": "酒店" } }, "sort": [ { "score": { "order": "desc" } }, { "price": { "order": "asc" } } ], "size": 100 }
-
-
運行結果如下所示
-
3.1.2、地理坐標排序
-
地理坐標排序跟上面的普通欄位排序略有不同,DSL語法格式如下所示
-
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance" : { "FIELD" : "緯度,經度", // 文檔中geo_point類型的欄位名、目標坐標點 "order" : "asc", // 排序方式 "unit" : "km" // 排序的距離單位 } } ] }
-
-
這個查詢的含義是
- ①、指定一個坐標,作為目標點
- ②、計算每一個文檔中,指定欄位(必須是
geo_point
類型)的坐標,到目標點的距離是多少 - ③、根據距離排序
-
示例
-
實現對酒店數據按照自己的位置坐標的距離升序排序
- 獲取自己位置的經緯度方式可以訪問這個網站:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/
-
假設我的位置坐標是:113.266022,22.995959,尋找周圍距離最近的酒店的DSL語句如下所示(
location
欄位也可以不使用大括弧,直接使用字元串)-
GET hotel/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance": { "location": { "lat": 22.995959, "lon": 113.266022 }, "order": "asc", "unit": "km" } } ] }
-
-
運行結果如下所示
-
3.1.3、普通欄位排序RestAPI
-
代碼如下所示
-
/** * 普通欄位排序查詢 */ @Test public void testCommonFieldSortQuery() throws IOException { // 1. 創建查詢請求對象 SearchRequest searchRequest = new SearchRequest("hotel"); // 2. 添加查詢請求體 searchRequest.source().query( QueryBuilders.matchAllQuery() ).sort( SortBuilders.fieldSort("score").order(SortOrder.DESC) ).sort( SortBuilders.fieldSort("price").order(SortOrder.ASC) ); // 3. 執行查詢,獲取響應結果 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 4. 處理響應數據 handlerCommonResponse(response); } /** * 用來處理響應數據(相當於解析返回的JSON數據) * @param response */ private void handlerCommonResponse(SearchResponse response) throws JsonProcessingException { // 1. 得到命中的數量(即總記錄數量) SearchHits hits = response.getHits(); long totalCount = hits.getTotalHits().value;// 總記錄數 System.out.println("總記錄數量為:" + totalCount); // 2. 獲取本次查詢出來的列表數據 SearchHit[] hitsArray = hits.getHits(); for (SearchHit hit : hitsArray) { Object[] sortValues = hit.getSortValues(); if (sortValues.length > 0) { System.out.println("當前酒店得分為【" + sortValues[0] + "】"); System.out.println("當前酒店價格為【" + sortValues[1] + "】"); } // 得到json字元串 String json = hit.getSourceAsString(); // 將json字元串轉換為實體類對象 HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class); System.out.println(hotelDoc); } }
-
-
3.1.4、地理坐標排序RestAPI
-
代碼如下所示
-
/** * 地理坐標排序測試 */ @Test public void testGeoDistanceSortQuery() throws IOException { // 1. 創建查詢請求體 SearchRequest searchRequest = new SearchRequest("hotel"); // 2. 添加查詢請求體 searchRequest.source().query( QueryBuilders.matchAllQuery() ).sort( SortBuilders.geoDistanceSort( "location", new GeoPoint(22.995959,113.266022) ).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS) ); // 3. 執行查詢,獲取響應數據 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 4. 處理響應結果 handlerGeoResponse(response); } /** * 用來處理響應數據(相當於解析返回的JSON數據) * @param response */ private void handlerGeoResponse(SearchResponse response) throws JsonProcessingException { // 1. 得到命中的數量(即總記錄數量) SearchHits hits = response.getHits(); long totalCount = hits.getTotalHits().value;// 總記錄數 System.out.println("總記錄數量為:" + totalCount); // 2. 獲取本次查詢出來的列表數據 SearchHit[] hitsArray = hits.getHits(); for (SearchHit hit : hitsArray) { Object[] sortValues = hit.getSortValues(); if (sortValues.length > 0) { System.out.println("距離當前位置【" + sortValues[0] + "】公裡"); } // 得到json字元串 String json = hit.getSourceAsString(); // 將json字元串轉換為實體類對象 HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class); System.out.println(hotelDoc); } }
-
3.2、分頁
- Elasticsearch預設情況下只返回top10的數據。而如果要查詢更多數據就需要修改分頁參數了
- Elasticsearch中通過修改
from
、size
參數來控制要返回的分頁結果from
:從第幾個文檔開始,從0開始size
:總共查詢幾個文檔
- 類似於MySQL中的
limit ?, ?
- DSL格式比較簡單,這裡就不多說了;只需要添加上面說的兩個參數即可
3.2.1、分頁查詢
- DSL語句如下圖所示
3.2.2、RestAPI
-
代碼如下所示
-
package com.coolman.hotel.test; import com.coolman.hotel.pojo.HotelDoc; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /** * 分頁 */ @SpringBootTest public class PageQueryTest { @Autowired private RestHighLevelClient restHighLevelClient; //jackson private final ObjectMapper objectMapper = new ObjectMapper(); /** * 分頁查詢測試 * @throws IOException */ @Test public void testPage() throws IOException { int from = 0; int size = 2; // 1. 創建查詢請求體 SearchRequest searchRequest = new SearchRequest("hotel"); // 2. 添加查詢請求體 searchRequest.source().query( QueryBuilders.matchAllQuery() ).from(from).size(size); // 3. 執行操作,獲取響應數據 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 4. 處理響應數據 handlerResponse(response); } /** * 用來處理響應數據(相當於解析返回的JSON數據) * @param response */ private void handlerResponse(SearchResponse response) throws JsonProcessingException { // 1. 得到命中的數量(即總記錄數量) SearchHits hits = response.getHits(); long totalCount = hits.getTotalHits().value;// 總記錄數 System.out.println("總記錄數量為:" + totalCount); // 2. 獲取本次查詢出來的列表數據 SearchHit[] hitsArray = hits.getHits(); for (SearchHit hit : hitsArray) { // 得到json字元串 String json = hit.getSourceAsString(); // 將json字元串轉換為實體類對象 HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class); System.out.println(hotelDoc); } } }
-
3.3、高亮顯示
3.3.1、高亮原理
- 高亮查詢的概念
- 我們在百度,京東等網站搜索的時候,關鍵字會變紅色,比較醒目,這就叫做高亮顯示
- 使用檢查工具查看源代碼可以發現
- 我們在百度,京東等網站搜索的時候,關鍵字會變紅色,比較醒目,這就叫做高亮顯示
- 高亮顯示的實現分為兩步
- 1)給文檔中的所有關鍵字都添加一個標簽,例如
<em>
標簽 - 2)頁面給
<em>
標簽編寫CSS樣式
- 1)給文檔中的所有關鍵字都添加一個標簽,例如
3.3.2、高亮顯示查詢DSL
-
語法格式
-
GET /indexName/_search { "query": { "match": { "FIELD": "TEXT" // 查詢條件,高亮一定要使用全文檢索查詢 } }, "highlight": { "fields": { // 指定要高亮的欄位 "FIELD": { "pre_tags": "<em>", // 用來標記高亮欄位的前置標簽 ,預設使用<em>標簽 "post_tags": "</em>" // 用來標記高亮欄位的後置標簽 } } } }
-
註意
- 高亮是對關鍵字高亮,因此搜索條件必須帶有關鍵字,而不能是範圍這樣的查詢。
- 預設情況下,高亮的欄位,必須與搜索指定的欄位一致,否則無法高亮
- 如果要對非搜索欄位高亮,則需要添加一個屬性:
required_field_match=false
-
-
示例
-
需求:讓酒店搜索結果的name欄位高亮顯示關鍵詞
-
DSL語句如下所示
-
# 高亮顯示 # 需求:讓酒店搜索結果的name欄位高亮顯示關鍵詞 # fields: 指定需要高亮顯示的欄位名 # pre_tags: 樣式首碼 不指定的話,就預設是<em>標簽 # post_tags:樣式尾碼 # require_field_match: # true 代表高亮欄位必須出現在條件中,才可以高亮 # false代表高亮欄位不一定要出現在條件,也可以高亮 GET hotel/_search { "query": { "match": { "name": "如家" } }, "highlight": { "fields": { "brand": {}, "name": {} }, "require_field_match": "false", "pre_tags": "<front color='red'>", "post_tags": "</front>" } }
-
-
查詢結果如下所示
- PS:這裡的DSL語句可以有些不同,比如fields可以是數組,然後
require_field_match
、pre_tags
、post_tags
都可以放在欄位中 - 變化有點多,這裡就不演示了,上面的是相當於全局配置的意思
- PS:這裡的DSL語句可以有些不同,比如fields可以是數組,然後
-
3.3.3、高亮顯示查詢RestAPI
-
代碼如下所示,可以參考DSL語句的格式來寫
-
package com.coolman.hotel.test; import com.coolman.hotel.pojo.HotelDoc; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Map; /** * 高亮顯示 */ @SpringBootTest public class HighLightQueryTest { @Autowired private RestHighLevelClient restHighLevelClient; //jackson private final ObjectMapper objectMapper = new ObjectMapper(); @Test public void testHighLightSearch() throws IOException { // 1. 創建查詢請求對象 SearchRequest searchRequest = new SearchRequest("hotel"); // 2. 添加查詢請求體 HighlightBuilder highlightBuilder = new HighlightBuilder(); // 添加高亮欄位方式1(暫時不知道什麼區別,瞭解就好) // highlightBuilder.fields().add(new HighlightBuilder.Field("name")); // highlightBuilder.fields().add(new HighlightBuilder.Field("brand")); // 添加高亮欄位方式2 highlightBuilder.field("name"); highlightBuilder.field("brand"); // 這裡相當於是全局的配置,也可以在上面添加配置,如 highlightBuilder.field("name").requireFieldMatch(false).postTags("...").preTags("..."); highlightBuilder.requireFieldMatch(false).preTags("<front color='red'>").postTags("</front>"); searchRequest.source().query( QueryBuilders.matchQuery("name", "如家") ).highlighter(highlightBuilder); // 3. 執行操作,獲取響應數據 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 4. 處理響應數據 handlerResponse(response); } /** * 用來處理響應數據(相當於解析返回的JSON數據) * @param response */ private void handlerResponse(SearchResponse response) throws JsonProcessingException { // 1. 得到命中的數量(即總記錄數量) SearchHits hits = response.getHits(); long totalCount = hits.getTotalHits().value;// 總記錄數 System.out.println("總記錄數量為:" + totalCount); // 2. 獲取本次查詢出來的列表數據 SearchHit[] hitsArray = hits.getHits(); for (SearchHit hit : hitsArray) { // 得到json字元串 String json = hit.getSourceAsString(); // 將json字元串轉換為實體類對象 HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class); // 處理高亮的情況 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); if (highlightFields.size() > 0) { if (highlightFields.get("name") != null) { Text nameHighLight = highlightFields.get("name").fragments()[0]; hotelDoc.setName(nameHighLight.toString()); } if (highlightFields.get("brand") != null) { Text brandHighLight = highlightFields.get("brand").fragments()[0]; hotelDoc.setBrand(brandHighLight.toString()); } } System.out.println(hotelDoc); } } }
-