06-ElasticSearch搜索结果处理

十念  金牌会员 | 2022-9-16 17:14:34 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 903|帖子 903|积分 2709

3、ElasticSearch搜索结果处理

3.1、排序


  • Elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序,可以排序的字段类型有如下几种

    • keyword类型
    • 数值类型
    • 地理坐标类型
    • 日期类型
    • ...

3.1.1、普通字段排序


  • keyword、数值、日期类型排序的语法基本一致;DSL语法如下所示

      1. GET /indexName/_search
      2. {
      3.   "query": {
      4.     "match_all": {}
      5.   },
      6.   "sort": [
      7.     {
      8.       "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
      9.     }
      10.   ]
      11. }
      复制代码

  • 排序条件是一个数组,也就是可以写多个排序条件;按照声明的排序,当第一个条件相等的时候,再按照第二个条件排序,依次类推
  • 示例

    • 酒店数据按照用户评价(score)降序排序,评价相同的按照价格(price)升序排序
    • DSL语句如下所示(match处是自定义的,也可以直接使用match_all)

        1. GET hotel/_search
        2. {
        3.   "query": {
        4.     "match": {
        5.       "name": "酒店"
        6.     }
        7.   },
        8.   "sort": [
        9.     {
        10.       "score": {
        11.         "order": "desc"
        12.       }
        13.     },
        14.     {
        15.       "price": {
        16.         "order": "asc"
        17.       }
        18.     }
        19.   ],
        20.   "size": 100
        21. }
        复制代码

    • 运行结果如下所示




3.1.2、地理坐标排序


  • 地理坐标排序跟上面的普通字段排序略有不同,DSL语法格式如下所示

      1. GET /indexName/_search
      2. {
      3.   "query": {
      4.     "match_all": {}
      5.   },
      6.   "sort": [
      7.     {
      8.       "_geo_distance" : {
      9.           "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
      10.           "order" : "asc", // 排序方式
      11.           "unit" : "km" // 排序的距离单位
      12.       }
      13.     }
      14.   ]
      15. }
      复制代码

  • 这个查询的含义是

    • ①、指定一个坐标,作为目标点
    • ②、计算每一个文档中,指定字段(必须是geo_point类型)的坐标,到目标点的距离是多少
    • ③、根据距离排序

  • 示例

    • 实现对酒店数据按照自己的位置坐标的距离升序排序

    • 假设我的位置坐标是:113.266022,22.995959,寻找周围距离最近的酒店的DSL语句如下所示(location字段也可以不使用大括号,直接使用字符串)

        1. GET hotel/_search
        2. {
        3.   "query": {
        4.     "match_all": {}
        5.   },
        6.   "sort": [
        7.     {
        8.       "_geo_distance": {
        9.         "location": {
        10.           "lat": 22.995959,
        11.           "lon": 113.266022
        12.         },
        13.         "order": "asc",
        14.         "unit": "km"
        15.       }
        16.     }
        17.   ]
        18. }
        复制代码

    • 运行结果如下所示




3.1.3、普通字段排序RestAPI


  • 代码如下所示

      1.     /**
      2.      * 普通字段排序查询
      3.      */
      4.     @Test
      5.     public void testCommonFieldSortQuery() throws IOException {
      6.         // 1. 创建查询请求对象
      7.         SearchRequest searchRequest = new SearchRequest("hotel");
      8.         // 2. 添加查询请求体
      9.         searchRequest.source().query(
      10.                 QueryBuilders.matchAllQuery()
      11.         ).sort(
      12.                 SortBuilders.fieldSort("score").order(SortOrder.DESC)
      13.         ).sort(
      14.                 SortBuilders.fieldSort("price").order(SortOrder.ASC)
      15.         );
      16.         // 3. 执行查询,获取响应结果
      17.         SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
      18.         // 4. 处理响应数据
      19.         handlerCommonResponse(response);
      20.     }
      21.     /**
      22.      * 用来处理响应数据(相当于解析返回的JSON数据)
      23.      * @param response
      24.      */
      25.     private void handlerCommonResponse(SearchResponse response) throws JsonProcessingException {
      26.         // 1. 得到命中的数量(即总记录数量)
      27.         SearchHits hits = response.getHits();
      28.         long totalCount = hits.getTotalHits().value;// 总记录数
      29.         System.out.println("总记录数量为:" + totalCount);
      30.         // 2. 获取本次查询出来的列表数据
      31.         SearchHit[] hitsArray = hits.getHits();
      32.         for (SearchHit hit : hitsArray) {
      33.             Object[] sortValues = hit.getSortValues();
      34.             if (sortValues.length > 0) {
      35.                 System.out.println("当前酒店得分为【" + sortValues[0] + "】");
      36.                 System.out.println("当前酒店价格为【" + sortValues[1] + "】");
      37.             }
      38.             // 得到json字符串
      39.             String json = hit.getSourceAsString();
      40.             // 将json字符串转换为实体类对象
      41.             HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
      42.             System.out.println(hotelDoc);
      43.         }
      44.     }
      复制代码


3.1.4、地理坐标排序RestAPI


  • 代码如下所示

      1.     /**
      2.      * 地理坐标排序测试
      3.      */
      4.     @Test
      5.     public void testGeoDistanceSortQuery() throws IOException {
      6.         // 1. 创建查询请求体
      7.         SearchRequest searchRequest = new SearchRequest("hotel");
      8.         // 2. 添加查询请求体
      9.         searchRequest.source().query(
      10.                 QueryBuilders.matchAllQuery()
      11.         ).sort(
      12.                 SortBuilders.geoDistanceSort(
      13.                         "location",
      14.                         new GeoPoint(22.995959,113.266022)
      15.                 ).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS)
      16.         );
      17.         // 3. 执行查询,获取响应数据
      18.         SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
      19.         // 4. 处理响应结果
      20.         handlerGeoResponse(response);
      21.     }
      22.     /**
      23.      * 用来处理响应数据(相当于解析返回的JSON数据)
      24.      * @param response
      25.      */
      26.     private void handlerGeoResponse(SearchResponse response) throws JsonProcessingException {
      27.         // 1. 得到命中的数量(即总记录数量)
      28.         SearchHits hits = response.getHits();
      29.         long totalCount = hits.getTotalHits().value;// 总记录数
      30.         System.out.println("总记录数量为:" + totalCount);
      31.         // 2. 获取本次查询出来的列表数据
      32.         SearchHit[] hitsArray = hits.getHits();
      33.         for (SearchHit hit : hitsArray) {
      34.             Object[] sortValues = hit.getSortValues();
      35.             if (sortValues.length > 0) {
      36.                 System.out.println("距离当前位置【" + sortValues[0] + "】公里");
      37.             }
      38.             // 得到json字符串
      39.             String json = hit.getSourceAsString();
      40.             // 将json字符串转换为实体类对象
      41.             HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
      42.             System.out.println(hotelDoc);
      43.         }
      44.     }
      复制代码

3.2、分页


  • Elasticsearch默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数
  • Elasticsearch中通过修改from、size参数来控制要返回的分页结果

    • from:从第几个文档开始,从0开始
    • size:总共查询几个文档

  • 类似于MySQL中的limit ?, ?
  • DSL格式比较简单,这里就不多说了;只需要添加上面说的两个参数即可
3.2.1、分页查询


  • DSL语句如下图所示



3.2.2、RestAPI


  • 代码如下所示

      1. package com.coolman.hotel.test;
      2. import com.coolman.hotel.pojo.HotelDoc;
      3. import com.fasterxml.jackson.core.JsonProcessingException;
      4. import com.fasterxml.jackson.databind.ObjectMapper;
      5. import org.elasticsearch.action.search.SearchRequest;
      6. import org.elasticsearch.action.search.SearchResponse;
      7. import org.elasticsearch.client.RequestOptions;
      8. import org.elasticsearch.client.RestHighLevelClient;
      9. import org.elasticsearch.index.query.QueryBuilder;
      10. import org.elasticsearch.index.query.QueryBuilders;
      11. import org.elasticsearch.search.SearchHit;
      12. import org.elasticsearch.search.SearchHits;
      13. import org.junit.jupiter.api.Test;
      14. import org.springframework.beans.factory.annotation.Autowired;
      15. import org.springframework.boot.test.context.SpringBootTest;
      16. import java.io.IOException;
      17. /**
      18. * 分页
      19. */
      20. @SpringBootTest
      21. public class PageQueryTest {
      22.     @Autowired
      23.     private RestHighLevelClient restHighLevelClient;
      24.     //jackson
      25.     private final ObjectMapper objectMapper = new ObjectMapper();
      26.     /**
      27.      * 分页查询测试
      28.      * @throws IOException
      29.      */
      30.     @Test
      31.     public void testPage() throws IOException {
      32.         int from = 0;
      33.         int size = 2;
      34.         // 1. 创建查询请求体
      35.         SearchRequest searchRequest = new SearchRequest("hotel");
      36.         // 2. 添加查询请求体
      37.         searchRequest.source().query(
      38.                 QueryBuilders.matchAllQuery()
      39.         ).from(from).size(size);
      40.         // 3. 执行操作,获取响应数据
      41.         SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
      42.         // 4. 处理响应数据
      43.         handlerResponse(response);
      44.     }
      45.     /**
      46.      * 用来处理响应数据(相当于解析返回的JSON数据)
      47.      * @param response
      48.      */
      49.     private void handlerResponse(SearchResponse response) throws JsonProcessingException {
      50.         // 1. 得到命中的数量(即总记录数量)
      51.         SearchHits hits = response.getHits();
      52.         long totalCount = hits.getTotalHits().value;// 总记录数
      53.         System.out.println("总记录数量为:" + totalCount);
      54.         // 2. 获取本次查询出来的列表数据
      55.         SearchHit[] hitsArray = hits.getHits();
      56.         for (SearchHit hit : hitsArray) {
      57.             // 得到json字符串
      58.             String json = hit.getSourceAsString();
      59.             // 将json字符串转换为实体类对象
      60.             HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
      61.             System.out.println(hotelDoc);
      62.         }
      63.     }
      64. }
      复制代码

3.3、高亮显示

3.3.1、高亮原理


  • 高亮查询的概念

    • 我们在百度,京东等网站搜索的时候,关键字会变红色,比较醒目,这就叫做高亮显示



    • 使用检查工具查看源代码可以发现




  • 高亮显示的实现分为两步

    • 1)给文档中的所有关键字都添加一个标签,例如<em>标签
    • 2)页面给<em>标签编写CSS样式

3.3.2、高亮显示查询DSL


  • 语法格式

      1. GET /indexName/_search
      2. {
      3.   "query": {
      4.     "match": {
      5.       "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
      6.     }
      7.   },
      8.   "highlight": {
      9.     "fields": { // 指定要高亮的字段
      10.       "FIELD": {
      11.         "pre_tags": "<em>",  // 用来标记高亮字段的前置标签 ,默认使用<em>标签
      12.         "post_tags": "</em>" // 用来标记高亮字段的后置标签
      13.       }
      14.     }
      15.   }
      16. }
      复制代码
    • 注意

      • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
      • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
      • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false


  • 示例

    • 需求:让酒店搜索结果的name字段高亮显示关键词
    • DSL语句如下所示

        1. # 高亮显示
        2. # 需求:让酒店搜索结果的name字段高亮显示关键词
        3. # fields: 指定需要高亮显示的字段名
        4. # pre_tags: 样式前缀        不指定的话,就默认是<em>标签
        5. # post_tags:样式后缀
        6. # require_field_match:
        7. # true 代表高亮字段必须出现在条件中,才可以高亮
        8. # false代表高亮字段不一定要出现在条件,也可以高亮
        9. GET hotel/_search
        10. {
        11.   "query": {
        12.     "match": {
        13.       "name": "如家"
        14.     }
        15.   },
        16.   "highlight": {
        17.     "fields": {
        18.       "brand": {},
        19.       "name": {}
        20.     },
        21.     "require_field_match": "false",
        22.     "pre_tags": "<front color='red'>",
        23.     "post_tags": "</front>"
        24.   }
        25. }
        复制代码

    • 查询结果如下所示


      • PS:这里的DSL语句可以有些不同,比如fields可以是数组,然后require_field_match、pre_tags、post_tags都可以放在字段中
      • 变化有点多,这里就不演示了,上面的是相当于全局配置的意思


3.3.3、高亮显示查询RestAPI


  • 代码如下所示,可以参考DSL语句的格式来写

      1. package com.coolman.hotel.test;
      2. import com.coolman.hotel.pojo.HotelDoc;
      3. import com.fasterxml.jackson.core.JsonProcessingException;
      4. import com.fasterxml.jackson.databind.ObjectMapper;
      5. import org.elasticsearch.action.search.SearchRequest;
      6. import org.elasticsearch.action.search.SearchResponse;
      7. import org.elasticsearch.client.RequestOptions;
      8. import org.elasticsearch.client.RestHighLevelClient;
      9. import org.elasticsearch.common.text.Text;
      10. import org.elasticsearch.index.query.QueryBuilder;
      11. import org.elasticsearch.index.query.QueryBuilders;
      12. import org.elasticsearch.search.SearchHit;
      13. import org.elasticsearch.search.SearchHits;
      14. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
      15. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
      16. import org.junit.jupiter.api.Test;
      17. import org.springframework.beans.factory.annotation.Autowired;
      18. import org.springframework.boot.test.context.SpringBootTest;
      19. import java.io.IOException;
      20. import java.util.Collection;
      21. import java.util.Collections;
      22. import java.util.Map;
      23. /**
      24. * 高亮显示
      25. */
      26. @SpringBootTest
      27. public class HighLightQueryTest {
      28.     @Autowired
      29.     private RestHighLevelClient restHighLevelClient;
      30.     //jackson
      31.     private final ObjectMapper objectMapper = new ObjectMapper();
      32.     @Test
      33.     public void testHighLightSearch() throws IOException {
      34.         // 1. 创建查询请求对象
      35.         SearchRequest searchRequest = new SearchRequest("hotel");
      36.         // 2. 添加查询请求体
      37.         HighlightBuilder highlightBuilder = new HighlightBuilder();
      38.         // 添加高亮字段方式1(暂时不知道什么区别,了解就好)
      39. //        highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
      40. //        highlightBuilder.fields().add(new HighlightBuilder.Field("brand"));
      41.         // 添加高亮字段方式2
      42.         highlightBuilder.field("name");
      43.         highlightBuilder.field("brand");
      44.         // 这里相当于是全局的配置,也可以在上面添加配置,如  highlightBuilder.field("name").requireFieldMatch(false).postTags("...").preTags("...");
      45.         highlightBuilder.requireFieldMatch(false).preTags("<front color='red'>").postTags("</front>");
      46.         searchRequest.source().query(
      47.                 QueryBuilders.matchQuery("name", "如家")
      48.         ).highlighter(highlightBuilder);
      49.         // 3. 执行操作,获取响应数据
      50.         SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
      51.         // 4. 处理响应数据
      52.         handlerResponse(response);
      53.     }
      54.     /**
      55.      * 用来处理响应数据(相当于解析返回的JSON数据)
      56.      * @param response
      57.      */
      58.     private void handlerResponse(SearchResponse response) throws JsonProcessingException {
      59.         // 1. 得到命中的数量(即总记录数量)
      60.         SearchHits hits = response.getHits();
      61.         long totalCount = hits.getTotalHits().value;// 总记录数
      62.         System.out.println("总记录数量为:" + totalCount);
      63.         // 2. 获取本次查询出来的列表数据
      64.         SearchHit[] hitsArray = hits.getHits();
      65.         for (SearchHit hit : hitsArray) {
      66.             // 得到json字符串
      67.             String json = hit.getSourceAsString();
      68.             // 将json字符串转换为实体类对象
      69.             HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
      70.             // 处理高亮的情况
      71.             Map<String, HighlightField> highlightFields = hit.getHighlightFields();
      72.             if (highlightFields.size() > 0) {
      73.                 if (highlightFields.get("name") != null) {
      74.                     Text nameHighLight = highlightFields.get("name").fragments()[0];
      75.                     hotelDoc.setName(nameHighLight.toString());
      76.                 }
      77.                 if (highlightFields.get("brand") != null) {
      78.                     Text brandHighLight = highlightFields.get("brand").fragments()[0];
      79.                     hotelDoc.setBrand(brandHighLight.toString());
      80.                 }
      81.             }
      82.             System.out.println(hotelDoc);
      83.         }
      84.     }
      85. }
      复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表