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); } } }
-
来源:https://www.cnblogs.com/OnlyOnYourself-lzw/p/16570857.html
本站部分图文来源于网络,如有侵权请联系删除。