一、DSL查询文档

1、DSL查询分类

ES提供了基于JSON的DSL语言定义的查询
DSL Query常见查询分类

  • 查询所有
    • 查询所有数据
      • match_all
  • 全文检索查询full text
    • 利用分词器对用户输入内容分词,然后去倒排索引库中匹配
      • match
      • multi_match
  • 精确查询
    • 根据精确词条值查找数据(不分词),一般是查找keyword、数值、日期、boolean等类型字段
      • term
      • range
  • 地理查询geo
    • 根据经纬度查询
      • geo_bounding_box
      • geo_distance
  • 复合查询component

    • 可将各种查询条件组合起来,合并查询条件
      • bool
      • function_score

        2、查询基本语法

        1. GET /索引库名/_search
        2. {
        3. "query": {
        4. "查询类型": {
        5. "查询条件": "条件值"
        6. }
        7. }
        8. }
  • 查询默认只显示10条数据信息(Kibana)

    3、全文检索查询

    全文检索查询

  • 对用户输入的内容进行分词

  • 常用于搜索框搜索

分类

  • match查询

    • 对输入内容分词,再倒排索引检索
      • 与查询条件(输入内容)越匹配,得分越高,显示越靠前
    • 根据一个字段查询
      1. GET /索引库名/_search
      2. {
      3. "query": {
      4. "match": {
      5. "FIELD": "TEXT"
      6. }
      7. }
      8. }
      • 大写表示字段/值是可变的,是自己索引库中的字段和值
      • 小写表示固定格式,不可修改
  • multi_match查询(不建议使用)

    • 允许同时查询多个字段
      • operator设置多字段的关系
        • 与and
        • 或or
    • 字段越多,性能越差
      1. GET /索引库名/_search
      2. {
      3. "query": {
      4. "multi_match": {
      5. "query": "TEXT",
      6. "fields": ["FIELD1", " FIELD12"]
      7. }
      8. }
      9. }

      4、精准查询

      精确查询
  • 一般是查找keyword、数值、日期、boolean等类型字段

  • 不会对搜索条件分词

分类

  • term查询

    • 根据词条精确查询
      • 一般查询keyword类型、数值类型、布尔类型、日期类型字段
      1. GET /索引库名/_search
      2. {
      3. "query": {
      4. "term": {
      5. "FIELD": {
      6. "value": "VALUE"
      7. }
      8. }
      9. }
      10. }
  • range查询

    • 根据值的范围精确查询
      • 一般查询数值、日期的范围
    • GET /索引库名/_search
      {
      "query": {
      "range": {
      "FIELD": {
       "gte": 数值,
       "lte": 数值
      }
      }
      }
      }
      
      • gte大于或等于 | gt大于
      • lte小于或等于 | lt小于

        5、地理坐标查询

        地理坐标查询
  • 根据经纬度查询

分类

  • geo_bounding_box查询(不常用)

    • 查询geo_point值落在某个矩形范围的所有文档(矩形范围)
    • GET /indexName/_search
      {
      "query": {
      "geo_bounding_box": {
      "FIELD": {
       "top_left": {
         "lat": 经度值,
         "lon": 纬度值
       },
       "bottom_right": {
         "lat": 经度值,
         "lon": 纬度值
       }
      }
      }
      }
      }
      
      • top_left左上
        • lat经度
        • lon纬度
      • bottom_right右下
  • geo_distance查询

    • 查询到指定中心点小于某个距离值的所有文档(圆形范围)
    • GET /索引库名称/_search
      {
      "query": {
      "geo_distance": {
      "distance": "距离范围",
      "FIELD": "纬度值, 经度值"
      }
      }
      }
      
      • distance距离范围
        • 即⚪的半径值
      • 纬度在前,经度在后

        6、复合查询

        复合(compound)查询
  • 将其它简单查询组合起来,实现更复杂的搜索逻辑

    6.1 算分函数查询

    fuction score查询(算分函数查询)

  • 可以控制文档相关性算分,控制文档排名

  • GET /索引库名称/_search
    {
    "query": {
    "function_score": {
      "query": { "match": {"all": "外滩"} },
      "functions": [
        {
          "filter": {"term": {"id": "1"}},
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
    }
    }
    
    • query原始查询条件
      • 包含原始算分
    • filter过滤条件
      • 符合条件的文档会被重新算分
    • weight(算分函数的一种)
      • 算分函数
        • 符合过滤条件的文档根据算分函数运算,得到函数算分
        • 常见的算分函数
          • weight:给一个常量值,作为函数结果(function score)
          • field_value_factor:用文档中的某个字段值作为函数结果
          • random_score:随机生成一个值,作为函数结果
          • cript_score:自定义计算公式,公式结果作为函数结果
    • boost_mode加权模式
      • 默认相乘multiply(算分函数和相关性算法(原始查询得分)相乘)
      • 分类
        • multiply:两者相乘
        • replace:用function score(算法函数)替换query score(相关性算法)
        • 其它:sumavgmaxmin

算分函数查询流程

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  2. 根据过滤条件,过滤文档
  3. 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  4. 将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果作为相关性算分

相关性算分算法

  • 当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列
  • 分类

    • TF-IDF(ES5.0之前)
      • 会随着词频增加而越来越大
    • BM25(ES5.0之后)
      • 会随着词频增加而增大,但增长曲线会趋于水平

        6.2 布尔查询

        布尔查询
  • 一个或多个查询子句的组合。

  • 子查询的组合方式

    • must(and)
      • 必须匹配每个子查询
      • 参与相关度算分
      • must字段有且只能有一个,条件可有多个
    • should(or)
      • 选择性匹配子查询
    • must_not(!)
      • 必须不匹配,不参与算分
    • filter(and)
      • 必须匹配(筛选条件)
      • 不参与相关度算分
        GET /索引库名称/_search
        {
        "query": {
        "bool": {
        "must": [
        {
          "查询方式": {
            "字段名": "值" 
          }
        } 
        ],
        "should": [
        {
          "查询方式": {
            "字段名": "值" 
          }
        }
        ],
        "must_not": [
        { 
          "查询方式": { 
            "字段名": "值"
          }
        }
        ],
        "filter": [
        { 
          "查询方式": {
            "字段名": "值"
          }
        }
        ]
        }
        }
        }
        

        二、搜索结果处理

        搜索结果处理的关键字(sort/(from, size)/highlight)与查询关键字(query)同级

        1、排序

        ES支持对搜索结果排序
  • 默认根据相关度算分(_score)来排序sort

  • 自定义排序方式

    • 根据排序字段按照指定排序方式进行排序
    • 排序字段类型
      • keyword类型、数值类型、地理坐标类型、日期类型等
      • 分词字段不支持排序
    • 排序方式
      • ASC升序
      • DESC降序
    • 自定义排序方式不进行相关度算法
    • 排序结果有个排序分值sort

      2、分页

      ES支持对搜索结果分页
  • 默认情况下只返回top10的数据

    • 如果要查询更多数据就需要修改分页参数
  • 自定义分页
    • ES中通过修改fromsize参数来控制要返回的分页结果
      • from
        • 分页开始位置
        • 默认0
      • size
        • 显示条数

深度分页问题

  • 默认上限10000(from + size)
  • from : m, size : n
  • 实际开发中,所有数据分散在集群中
  • 从集群的每一个分片上都查出前m条数据,将查询到的数据聚合放到一个结果集中
  • 再从该结果集中从第m条数据开始,获取n条数据

ES提供解决深度分页方案-无上限查询

  1. search after(官方推荐)
    • 无上限查询
    • 分页时需要排序
    • 原理是从上一次的排序值开始,查询下一页数据
      • 不支持翻页
  2. scroll(不推荐)
    • 滚动条查询
    • 原理是将排序数据形成快照,保存在内存
      • 搜索结果的数据不实时不准确

        3、高亮

        高亮(在搜索前提下高亮)
  • 在搜索结果中把搜索关键字突出显示
  • 原理
    • 将搜索结果中的关键字用标签标记出来
    • 在页面中给标签添加css样式
  • 默认搜索哪个字段则哪个字段高亮
    • 当搜索字段和高亮字段不匹配时
      • 需要手动设置highlight.require_field_match : “false”
  • 高亮结果放到查询结果的highlight字段中,表示哪个字段被高亮了

高亮三要素

  1. 指定要高亮的字段
  2. 标记高亮字段的前置标签
  3. 标记高亮字段的后置标签

    三、RestClient查询文档

    0、初始化

    查询的基本步骤

  4. 创建SearchRequest对象request

  5. request.source().Xxx()调用Xxx方法根据指定查询方式进行查询
    • Xxx()方法即DSL查询方式
      • 传入的参数是QueryBuilders.Yyy()
      • 即查询方式Xxx具体的查询方式
  6. 发送请求,得到结果
    • 调用RestHighLevelClient对象的.search()方法
  7. 解析结果
    • 参考JSON结果,从外到内,逐层解析

初始化

public class HotelSearchTest {
    RestHighLevelClient rhlClient;

    /**
     * TODO:初始化ES客户端
     */
    @BeforeEach
    public void initClient(){
        rhlClient = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.200.130", 9200, "http")
                )
        );
    }

    /**
     * TODO:关闭ES客户端
     */
    @AfterEach
    public void closeClient(){
        try {
            rhlClient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

抽取解析响应的方法

/**
    * TODO:抽取出解析响应的方法
    * @param response
*/
public void handleResponse(SearchResponse response){
    //3、解析响应结果(需要先获取头hits,再继续往下获取想要的数据)
    SearchHits hits = response.getHits();
    //获取响应结果中的总条数
    long totalHits = hits.getTotalHits().value;
    System.out.println("满足查询条件的结果数量:" + totalHits);
    //获取响应结果中的文档内容,即source部分(是一个数组)
    SearchHit[] docArr = hits.getHits();
    //遍历文档列表
    for (SearchHit doc : docArr) {
        //获取每个文档中的source字段内容(是json类型的字符串,需要反序列化为Java对象)
        String jsonStr = doc.getSourceAsString();
        //json类型数据反序列化为Java对象数据
        HotelDoc hotelDoc = JSON.parseObject(jsonStr, HotelDoc.class);
        System.out.println("文档内容:" + hotelDoc);
    }
}

1、快速入门

/**
    * TODO:快速入门
    * @throws IOException
*/
@Test
public void testMatchAllQuery() throws IOException {
    //1、创建搜索请求对象
    SearchRequest request = new SearchRequest("hotel");
    //设置query条件matchAll
    request.source().query(QueryBuilders.matchAllQuery());
    //2、执行client对应的搜索请求方法
    SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
    //3、解析响应结果(需要先获取头hits,再继续往下获取想要的数据)
    SearchHits hits = response.getHits();
    //获取响应结果中的总条数
    long size = hits.getTotalHits().value;
    System.out.println("满足查询条件的结果数量:" + size);
    //获取响应结果中的文档内容,即source部分(是一个数组)
    SearchHit[] docArr = hits.getHits();
    //遍历文档列表
    for (SearchHit doc : docArr) {
        //获取每个文档中的source字段内容(是json类型的字符串,需要反序列化为Java对象)
        String jsonStr = doc.getSourceAsString();
        //json类型数据反序列化为Java对象数据
        HotelDoc hotelDoc = JSON.parseObject(jsonStr, HotelDoc.class);
        System.out.println("文档内容:" + hotelDoc);
    }
}

整个响应结果就是response

  • 可以获取总数量、文档列表等…

    2、全文检索查询match

    /**
      * TODO:全文搜索查询match
      * @throws IOException
    */
    @Test
    public void testMatchQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //设置query条件match
      request.source().query(QueryBuilders.matchQuery("all", "如家"));
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    3、精确查询

    3.1 精确查询-term查询

    /**
      * TODO:精确查询-词条查询term
      * @throws IOException
    */
    @Test
    public void testTermQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //设置query条件term
      request.source().query(QueryBuilders.termQuery("city", "杭州"));
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    3.2 精确查询-range查询

    /**
      * TODO:精确查询-范围查询range
      * @throws IOException
    */
    @Test
    public void testRangeQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //设置query条件range
      request.source().query(QueryBuilders.rangeQuery("price").gte(188).lte(588));
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    4、复合查询

    /**
      * TODO:复合查询-布尔查询
      * name 外滩如家
      * price 188-588
      * city 不可以是杭州
      * @throws IOException
    */
    @Test
    public void testBoolQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //创建bool条件,并设置条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
      boolQueryBuilder.must(QueryBuilders.matchQuery("name", "外滩如家"));
      boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(188).lte(588));
      boolQueryBuilder.mustNot(QueryBuilders.termQuery("city", "杭州"));
      //将条件设置的结果给请求对象
      request.source().query(boolQueryBuilder);
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    5、排序、分页、高亮

    5.1 排序

    /**
      * TODO:排序查询
      * name 外滩如家
      * price 188-588
      * city 不可以是杭州
      * @throws IOException
    */
    @Test
    public void testSortQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //创建bool条件,并设置条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
      boolQueryBuilder.must(QueryBuilders.matchQuery("name", "外滩如家"));
      boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(188).lte(588));
      boolQueryBuilder.mustNot(QueryBuilders.termQuery("city", "杭州"));
      //将条件设置的结果给请求对象
      request.source().query(boolQueryBuilder);
      //TODO:排序(设置:需要排序的字段, 升序/降序)
      request.source().sort("price", SortOrder.DESC);
      request.source().sort("score", SortOrder.DESC);
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    5.2 分页

    /**
      * TODO:分页查询
      * name 外滩如家
      * price 188-588
      * city 不可以是杭州
      * @throws IOException
    */
    @Test
    public void testPageQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //创建bool条件,并设置条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
      boolQueryBuilder.must(QueryBuilders.matchQuery("name", "外滩如家"));
      boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(188).lte(588));
      boolQueryBuilder.mustNot(QueryBuilders.termQuery("city", "杭州"));
      //将条件设置的结果给请求对象
      request.source().query(boolQueryBuilder);
      //排序(设置:需要排序的字段, 升序/降序)
      request.source().sort("price", SortOrder.DESC);
      request.source().sort("score", SortOrder.DESC);
      //TODO:分页
      int page = 1;
      int pageSize = 5;
      request.source().from((page - 1) * pageSize).size(pageSize);
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      handleResponse(response);
    }
    

    5.3 高亮

    /**
      * TODO:高亮查询
      * @throws IOException
    */
    @Test
    public void testHighlightQuery() throws IOException {
      //1、创建搜索请求对象
      SearchRequest request = new SearchRequest("hotel");
      //创建query条件,并设置条件
      request.source().query(QueryBuilders.matchQuery("all", "如家"));
      HighlightBuilder highlightBuilder = new HighlightBuilder();
      highlightBuilder.field("name"); //设置高亮字段
      highlightBuilder.preTags("<span>"); //设置前置标签
      highlightBuilder.postTags("</span>"); //设置后置标签
      highlightBuilder.requireFieldMatch(false); //关闭字段检查
      request.source().highlighter(highlightBuilder);
      //2、执行client对应的搜索请求方法
      SearchResponse response = rhlClient.search(request, RequestOptions.DEFAULT);
      //3、处理响应结果
      SearchHits hits = response.getHits();
      long totalHits = hits.getTotalHits().value;
      System.out.println("查询结果条数:" + totalHits);
      SearchHit[] docArr = hits.getHits();
      for (SearchHit doc : docArr) {
          String jsonStr = doc.getSourceAsString();
          HotelDoc hotelDoc = JSON.parseObject(jsonStr, HotelDoc.class);
          //处理高亮结果,用高亮结果替换原来的name属性
          Map<String, HighlightField> highlightFields = doc.getHighlightFields();
          if (!CollectionUtils.isEmpty(highlightFields)){
              //获取关于name字段的高亮结果
              HighlightField highlightField = highlightFields.get("name");
              if (highlightField != null){
                  String newName = highlightField.getFragments()[0].toString();
                  hotelDoc.setName(newName);
              }
          }
          System.out.println("高亮查询文档结果:" + hotelDoc);
      }
    }
    

    四、黑马旅游案例

    https://gitee.com/VanessaRain/hotel-demo.git(包含拓展任务)