06-ElasticSearch搜索结果处理

06-ElasticSearch搜索结果处理

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中通过修改fromsize参数来控制要返回的分页结果
    • 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样式

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_matchpre_tagspost_tags都可以放在字段中
      • 变化有点多,这里就不演示了,上面的是相当于全局配置的意思

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);
              }
          }
      }
      
      
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 06-ElasticSearch搜索结果处理