Elasticsearch提供了DSL ( Domain Specific Language)查询,就是以SON格式来定义查询条件

DSL查询可以分为两大类:

叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。

复合查询(Compound query clauses)︰以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。

在查询以后,还可以对查询的结果做处理,包括:

  • 排序:按照1个或多个字段值做排序
  • 分页:根据from和size做分页,类似MySQL
  • 高亮:对搜索结果中的关键字添加特殊样式,使其更加醒目·聚合:对搜索结果做数据统计以形成报表

快速入门

【Elasticsearch】-DSL查询(从零到起飞)_聚合

# 查询所有
GET /heima/_search
{
  "query": {
    "match_all": {}
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

叶子查询

叶子查询还可以进一步细分,常见的有:

全文检索(full text)查询:利用分词器对用户输入内容分词,然后去词条列表中匹配。例如:

  • match_query
  • multi_match_query

精确查询:不对用户输入内容分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。例如:

  • ids
  • range
  • term

地理(geo)查询:用于搜索地理位置,搜索方式很多。例如:

  • geo_distance
  • geo_bounding_box


match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:

# mach查询
GET /heima/_search
{
  "query": {
    "match": {
      "name": "脱脂牛奶"
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

【Elasticsearch】-DSL查询(从零到起飞)_数据_02

multi_match: 与match查询类似,只不过允许同时查询多个字段,语法:

【Elasticsearch】-DSL查询(从零到起飞)_聚合_03

精确查询

精确查询,英文是Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。

因此推荐查找keyword、数值、日期、boolean类型的字段。例如id、price、城市、地名、人名等作为一个整体才有含义的字段。

【Elasticsearch】-DSL查询(从零到起飞)_聚合_04

【Elasticsearch】-DSL查询(从零到起飞)_聚合_05

# term查询
GET /items/_search
{
  "query": {
    "term": {
      "brand": {
        "value": "德亚"
      }
    }
  }
}
# range查询
GET /items/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 5000,
        "lte": 10000
      }
    }
  }
}


# ids查询
GET /items/_search
{
  "query": {
    "ids": {
      "values": ["613359"]
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.

复合查询

复合查询大致可以分为两类:

第一类:基于逻辑运算组合叶子查询,实现组合条件,例如

  • bool

第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:

  • function_score
  • dis_max

布尔查询是一个或多个查询子句的组合。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

【Elasticsearch】-DSL查询(从零到起飞)_数据_06

排序和分页

elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序,也可以指定字段排序。可以排序字段类型有: keyword类型、数值类型、地理坐标类型、日期类型等。

排序

【Elasticsearch】-DSL查询(从零到起飞)_数据_07

# 排序查询
GET /items/_search
{
  "query": {
    "match_all": {}
  }, 
  "sort": [
    {
      "sold":  "desc"
    },
    {
      "price":  "asc"
    }
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

分页

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

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

【Elasticsearch】-DSL查询(从零到起飞)_数据_08

# 排序及分页查询
GET /items/_search
{
  "query": {
    "match_all": {}
  }, 
  "sort": [
    {
      "sold":  "desc"
    },
    {
      "price":  "asc"
    }
  ],
  "from": 0,
  "size": 10
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

深度分页问题

elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。查询数据时需要汇总各个分片的数据。

【Elasticsearch】-DSL查询(从零到起飞)_搜索_09

假如我们要查询第100页数据,每页查询10条:

那么实现的思路:首先对数据进行排序,然后再去找出990~1000名的数据

在底层是把每一片里的前1000个数据选出来,然后聚合重新排序选取前1000个,当我们的数据非常多并且页数非常靠后时,就会导致效率非常差

解决办法:

search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的式。

scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。

优点:没有查询上限,支持深度分页

缺点:只能向后逐页查询,不能随机翻页

场景:数据迁移,手机滚动查询

高亮显示

就是在搜索结果中把搜索关键字突出显示

【Elasticsearch】-DSL查询(从零到起飞)_数据_10

#高量
GET /items/_search
{
  "query": {
    "match": {
      "name": "脱脂牛奶"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

【Elasticsearch】-DSL查询(从零到起飞)_搜索_11

搜索的完整语法

【Elasticsearch】-DSL查询(从零到起飞)_搜索_12

JavaRestClient查询

快速入门

数据搜索的Java代码我们分为两部分:

  • 构建并发起请求
  • 解析查询结果

【Elasticsearch】-DSL查询(从零到起飞)_数据_13

@Test
    void testMachAll() throws IOException {
        //1.配置request对象
        SearchRequest request = new SearchRequest("items");
        //2.配置request参数
        request.source()
                .query(QueryBuilders.matchAllQuery());
        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        SearchHits searchHits = response.getHits();

        //4.1总条数
        long total = searchHits.getTotalHits().value;

        //4.2命中的数据
        SearchHit[] hits = searchHits.getHits();

        for (SearchHit hit : hits) {
            //获取source结果
            String json = hit.getSourceAsString();
            //转成对象
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
            System.out.println("doc = "+doc);
        }
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

构建查询条件

在JavaRestAPI中,所有类型的query查询条件都是由QueryBuilders来构建的:

【Elasticsearch】-DSL查询(从零到起飞)_搜索_14

【Elasticsearch】-DSL查询(从零到起飞)_聚合_15

【Elasticsearch】-DSL查询(从零到起飞)_聚合_16

@Test
    void testSearch() throws IOException {
        //1.配置request对象
        SearchRequest request = new SearchRequest("items");
        //2.配置request参数
        request.source().query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.matchQuery("name","脱脂牛奶"))
                        .filter(QueryBuilders.termQuery("brand","德亚"))
                        .filter(QueryBuilders.rangeQuery("price").lt(30000))
        );
        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

排序和分页

【Elasticsearch】-DSL查询(从零到起飞)_搜索_17

    @Test
    void testSortAndPage() throws IOException {
        //模拟前端传来的数据
        int pageNo = 1, pageSize =5;


        //1.配置request对象
        SearchRequest request = new SearchRequest("items");
        //2.配置request参数
        request.source().query(QueryBuilders.matchAllQuery());
        //分页
        request.source().from((pageNo-1)*pageSize).size(pageSize);
        //排序
        request.source()
                .sort("sold", SortOrder.DESC)
                .sort("price",SortOrder.ASC);
        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

高亮显示

【Elasticsearch】-DSL查询(从零到起飞)_聚合_18

【Elasticsearch】-DSL查询(从零到起飞)_数据_19

    @Test
    void testHighlight() throws IOException {
        //1.配置request对象
        SearchRequest request = new SearchRequest("items");
        //2.配置request参数
        request.source().query(QueryBuilders.matchQuery("name","脱脂牛奶"));
        //高量条件
        request.source().highlighter(SearchSourceBuilder.highlight().field("name"));
        //3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4.解析结果
        parseResponseResult(response);
    }
  
  
  private static void parseResponseResult(SearchResponse response){
        SearchHits searchHits = response.getHits();

        //4.1总条数
        long total = searchHits.getTotalHits().value;

        //4.2命中的数据
        SearchHit[] hits = searchHits.getHits();

        for (SearchHit hit : hits) {
            //获取source结果
            String json = hit.getSourceAsString();
            //转成对象
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);

            //处理高亮结果
            Map<String, HighlightField> hfs = hit.getHighlightFields();
            if(hfs!=null && !hfs.isEmpty()){
                //根据高亮字段名获取高亮结果
                HighlightField hf = hfs.get("name");
                //获取高亮结果,覆盖非高亮结果
                String hfName = hf.getFragments()[0].string();
                doc.setName(hfName);
            }
            System.out.println("doc = "+doc);
        }
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

数据聚合

聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:

桶( Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组
  • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

  • Avg:求平均值
  • Max:求最大值
  • Min:求最小值
  • Stats:同时求max. min.ava、sum等

管道( pipeline)聚合:其它聚合的结果为基础做聚合

注意:参与聚合的字段必须是Keyword、数值、日期、布尔的类型的字段

DSL聚合

我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合。

【Elasticsearch】-DSL查询(从零到起飞)_数据_20

GET /items/_search
{
  "size": 0, 
  "aggs": {
    "category_agg": {
      "terms": {
        "field": "category",
        "size": 20
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可。例如,我想知道价格高于3000元的手机品牌有哪些:

GET /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size": 0, 
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

除了对数据分组(Bucket)以外,我们还可以对每个Bucket内的数据进一步做数据计算和统计。例如:我想知道手机有哪些品牌,每个品牌的价格最小值、最大值、平均值。

GET /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size": 0, 
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 20
      },
      "aggs": {
        "stats_meric": {
          "stats": {
            "field": "price"
          }
        }
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

RestClient聚合

【Elasticsearch】-DSL查询(从零到起飞)_聚合_21

【Elasticsearch】-DSL查询(从零到起飞)_聚合_22

@Test
void testAgg() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("items");
    // 2.准备请求参数
    BoolQueryBuilder bool = QueryBuilders.boolQuery()
            .filter(QueryBuilders.termQuery("category", "手机"))
            .filter(QueryBuilders.rangeQuery("price").gte(300000));
    request.source().query(bool).size(0);
    // 3.聚合参数
    request.source().aggregation(
            AggregationBuilders.terms("brand_agg").field("brand").size(5)
    );
    // 4.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 5.解析聚合结果
    Aggregations aggregations = response.getAggregations();
    // 5.1.获取品牌聚合
    Terms brandTerms = aggregations.get("brand_agg");
    // 5.2.获取聚合中的桶
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 5.3.遍历桶内数据
    for (Terms.Bucket bucket : buckets) {
        // 5.4.获取桶内key
        String brand = bucket.getKeyAsString();
        System.out.print("brand = " + brand);
        long count = bucket.getDocCount();
        System.out.println("; count = " + count);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.