搜索引擎ElasticSearch7.x

2023/3/28 Install语法

# 搜索引擎ElasticSearch概述

全文检索是计算机程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置。当用户查询时根据建立的索引查找,类似于通过字典的检索字表查字的过程。

# 1、什么是ElasticSearch

ElasticSearch简称ES,是基于Apache Lucene构建的开源搜索引擎,是当前最流行的企业级搜索引擎。Lucene本身就可以被认为迄今为止性能最好的一款开源搜索引擎工具包,但是lucene的API相对复杂,需要深厚的搜索理论。很难集成到实际的应用中去。ES是采用java语言编写,提供了简单易用的RestFul API,开发者可以使用其简单的RestFul API,开发相关的搜索功能,从而避免lucene的复杂性。

# 2、ElasticSearch诞生

多年前,一个叫做Shay Banon的刚结婚不久的失业开发者,由于妻子要去伦敦学习厨师,他便跟着也去了。在他找工作的过程中,为了给妻子构建一个食谱的搜索引擎,他开始构建一个早期版本的Lucene

直接基于Lucene工作会比较困难,所以Shay开始抽象Lucene代码以便Java程序员可以在应用中添加搜索功能。他发布了他的第一个开源项目,叫做“Compass”。

后来Shay找到一份工作,这份工作处在高性能和内存数据网格的分布式环境中,因此高性能的、实时的、分布式的搜索引擎也是理所当然需要的。然后他决定重写Compass库使其成为一个独立的服务叫做Elasticsearch。

第一个公开版本出现在2010年2月,在那之后Elasticsearch已经成为Github上最受欢迎的项目之一,代码贡献者超过300人。一家主营Elasticsearch的公司就此成立,他们一边提供商业支持一边开发新功能,不过Elasticsearch将永远开源且对所有人可用。

目前国内大厂几乎无一不用Elasticsearch,阿里,腾讯,京东,美团等等.....

# 安装ElasticSearch和Kibana

传统方式安装ES是一件比较费劲的事情,使用Docker能够非常轻松的安装ElasticSearch。而Kibana是一个针对Elasticsearch的开源分析及可视化平台,使用Kibana可以查询、查看并与存储在ES索引的数据进行交互操作,使用Kibana能执行高级的数据分析,并能以图表、表格和地图的形式查看数据。

# 1、使用Docker方式安装ElasticSearch

# 1.获取镜像
docker pull elasticsearch:7.14.0

# 2.运行es
docker run -d -p 9200:9200 -p 9300:9300  -e "discovery.type=single-node"  elasticsearch:7.14.0
1
2
3
4
5

安装成功后用过端口9200进行访问,http://ip:9200/,如果出现如下JSON数据代表安装成功。

# 2、使用Docker方式安装Kibana

# 1.获取镜像
docker pull kibana:7.14.0

# 2.运行kibana
docker run -d  --name kibana -p 5601:5601 kibana:7.14.0

# 3.进入容器
docker exec -it kibana bash

# 4.修改ElasticSearch地址
vi /usr/share/kibana/config/kibana.yml

# 5.测试:重启kibana容器,访问 http://ip地址:5601
docker restart kibana
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# a、基于数据卷加载配置文件方式运行

  • a.从容器复制kibana配置文件出来
  • b.修改配置文件为对应ES服务器地址
  • c.通过数据卷加载配置文件方式启动
docker run -d -v /root/kibana.yml:/usr/share/kibana/config/kibana.yml  --name kibana -p 5601:5601 kibana:7.14.0
1

# b、测试kibana安装是否成功

访问 http://ip地址:5601,出现如下界面代表安装成功

# ES的核心概念与基本操作

和学习MySQL一样,学习MySQL我们需要理解库、表、字段等概念。那么学习ElasticSearch,我们也需要先学习其核心概念才能理解如何使用,主要有三个概念,如下所示:

  • 索引 Index

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个商品数据的索引,一个订单数据的索引,还有一个用户数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字.

  • 映射 Mapping

映射是定义一个文档和它所包含的字段如何被存储和索引的过程。在默认配置下,ES可以根据插入的数据自动地创建mapping,也可以手动创建mapping。 mapping中主要包括字段名、字段类型等

  • 文档 Document

文档是索引中存储的一条条数据。一条文档是一个可被索引的最小单元。ES中的文档采用了轻量级的JSON格式数据来表示。

# 1、索引的基本操作

索引只能被删除不能被修改,所以索引的操作包含增删查,不包含修改索引操作。首先我们需要打开输入命令的控制台。

# 创建索引
PUT /products

PUT /orders
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }
}

# 查看ES中的索引
GET /_cat/indices?v

# 删除索引
DELETE /products
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# a、添加索引

顶部会有警告信息,我们不用管

# b、查询索引

health:代表健康状态,图中有详细说明,red才是不能使用的,yellow和green都是可以正常使用的。

index开头是"."代表系统索引,剩下的才是我们创建的

pri代表主数据块,rep代表备用数据块,当主数据和备用数据都在同一台机器上,不能满足高可用,所以才提示yellow

接下来我们指定主数据块为1,备用数据块为0,发现已经health变成green了

# c、删除索引

# 2、映射的基本操作

我们再创建索引的时候,一般会将映射一同创建,索引与映射一般是不分家的。

# a、创建索引并指定映射

  • 字符串类型: keyword关键字、关键词,而text一般是一段文本
  • 数字类型:integer long
  • 小数类型:float double
  • 布尔类型:boolean
  • 日期类型:date
PUT /products
{ 
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }, 
  "mappings": {
    "properties": {
      "title":{
        "type": "keyword"
      },
      "price":{
        "type": "double"
      },
      "created_at":{
        "type": "date"
      },
      "description":{
        "type": "text"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# b、查看某个索引的映射信息

GET /products/_mapping
1

映射无法修改或删除,查看products索引的映射信息,返回数据如下:

{
  "products" : {
    "mappings" : {
      "properties" : {
        "created_at" : {
          "type" : "date"
        },
        "description" : {
          "type" : "text"
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "keyword"
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# ElasticSearch文档的基本操作

创建文档如果不指定id会默认创建,默认创建的id是UUID格式的

  • 添加文档——手动指定_id

注意路径,路径最后跟上的要和json数据中的id保持一致。

POST /products/_doc/1
{
  "id": 1,
  "title": "松鼠",
  "price": 0.5,
  "created_at": "2022-10-10",
  "description": "可爱的松鼠"
}
1
2
3
4
5
6
7
8
  • 添加文档——自动生成文档id
POST /products/_doc/
{
  "title":"iphone14",
  "price":8999.99,
  "created_at":"2021-09-15",
  "description":"iPhone 13屏幕采用6.8英寸OLED屏幕"
}
1
2
3
4
5
6
7

# 1、查询文档

GET /products/_doc/1
1

返回的数据如下:

{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "id" : 1,
    "title" : "松鼠",
    "price" : 0.5,
    "created_at" : "2022-10-10",
    "description" : "可爱的松鼠"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2、更新文档

PUT /products/_doc/1
{
  "title": "仓鼠"
}
1
2
3
4

更新后查询发现,映射只剩下title,之前的id、price等映射全都没有了

{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 5,
  "_seq_no" : 5,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "仓鼠"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 3、删除文档

DELETE /products/_doc/1
1

# 4、批量操作

批量索引两条文档

POST /products/_doc/_bulk
 	{"index":{"_id":"1"}}
  		{"title":"iphone14","price":8999.99,"created_at":"2021-09-15","description":"iPhone 13屏幕采用6.8英寸OLED屏幕"}
	{"index":{"_id":"2"}}
  		{"title":"iphone15","price":8999.99,"created_at":"2021-09-15","description":"iPhone 15屏幕采用10.8英寸OLED屏幕"}
1
2
3
4
5

更新文档同时删除文档

POST /products/_doc/_bulk
	{"update":{"_id":"1"}}
		{"doc":{"title":"iphone17"}}
	{"delete":{"_id":2}}
	{"index":{}}
		{"title":"iphone19","price":8999.99,"created_at":"2021-09-15","description":"iPhone 19屏幕采用61.8英寸OLED屏幕"}
1
2
3
4
5
6

批量时不会因为一个失败而全部失败,而是继续执行后续操作,在返回时按照执行的状态返回!

# ElasticSearch高级查询QueryDSL

搜索引擎ElasticSearch中提供了一种强大的检索数据方式,这种检索方式称之为Query DSL,Query DSL是利用Rest API传递JSON格式的请求体(Request Body)数据与ES进行交互,这种方式的丰富查询语法让ES检索变得更强大,更简洁。

GET /索引名/_doc/_search {json格式请求体数据}

测试数据

# 1.创建索引 映射
PUT /products/
{
  "mappings": {
    "properties": {
      "title":{
        "type": "keyword"
      },
      "price":{
        "type": "double"
      },
      "created_at":{
        "type":"date"
      },
      "description":{
        "type":"text"
      }
    }
  }
}
# 2.测试数据
PUT /products/_doc/_bulk
{"index":{}}
  {"title":"iphone12 pro","price":8999,"created_at":"2020-10-23","description":"iPhone 12 Pro采用超瓷晶面板和亚光质感玻璃背板,搭配不锈钢边框,有银色、石墨色、金色、海蓝色四种颜色。宽度:71.5毫米,高度:146.7毫米,厚度:7.4毫米,重量:187克"}
{"index":{}}
  {"title":"iphone12","price":4999,"created_at":"2020-10-23","description":"iPhone 12 高度:146.7毫米;宽度:71.5毫米;厚度:7.4毫米;重量:162克(5.73盎司) [5]  。iPhone 12设计采用了离子玻璃,以及7000系列铝金属外壳。"}
{"index":{}}
  {"title":"iphone13","price":6000,"created_at":"2021-09-15","description":"iPhone 13屏幕采用6.1英寸OLED屏幕;高度约146.7毫米,宽度约71.5毫米,厚度约7.65毫米,重量约173克。"}
{"index":{}}
  {"title":"iphone13 pro","price":8999,"created_at":"2021-09-15","description":"iPhone 13Pro搭载A15 Bionic芯片,拥有四种配色,支持5G。有128G、256G、512G、1T可选,售价为999美元起。"}
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

# 1、查询所有[match_all]

返回索引中的全部文档,第一行都是GET /products/_search,后面的案例将只展示json格式的数据,查询时第一行记得写。

GET /products/_search
{
    "query": {
    "match_all": {}
    }
}
1
2
3
4
5
6

# 2、关键词查询[term]

  • 通过使用term查询得知ES中默认使用分词器为`标准分词器(StandardAnalyzer),标准分词器对于英文单词分词,对于中文单字分词,所以说对中文的分词并不友好。
  • 过使用term查询得知,在ES的MappingType中keyword, date, integer, long, double, boolean OR ip 这些类型不分词,只有text类型分词。
{
 "query": {
   "term": {
     "price": {
       "value": 4999
     }
   }
 }
}
1
2
3
4
5
6
7
8
9

# 3、范围查询[range]

用来指定查询指定范围内的文档,gte代表大于,lte代表小于

{
  "query": {
    "range": {
      "price": {
        "gte": 1400,
        "lte": 9999
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# 4、前缀查询[prefix]

用来检索含有指定前缀的关键词的相关文档

{
  "query": {
    "prefix": {
      "title": {
        "value": "ipho"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9

# 5、通配符查询[wildcard]

通配符查询:?用来匹配一个任意字符,*用来匹配多个任意字符

{
  "query": {
    "wildcard": {
      "description": {
        "value": "iphon*"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9

# 6、多id查询[ids]

ids 关键字 : 值为数组类型,用来根据一组id获取多个对应的文档

{
  "query": {
    "ids": {
      "values": ["verUq3wBOTjuBizqAegi","vurUq3wBOTjuBizqAegk"]
    }
  }
}
1
2
3
4
5
6
7

# 7、模糊查询[fuzzy]

用来模糊查询含有指定关键字的文档

{
  "query": {
    "fuzzy": {
      "description": "iphooone"
    }
  }
}
1
2
3
4
5
6
7
  • 搜索关键词长度为 2 不允许存在模糊
  • 搜索关键词长度为3-5 允许一次模糊
  • 搜索关键词长度大于5 允许最大2模糊

# 8、布尔查询[bool]

用来组合多个条件实现复杂查询.

  • must: 相当于&& 同时成立

  • should: 相当于|| 成立一个就行

  • must_not: 相当于! 不能满足任何一个

{
  "query": {
    "bool": {
      "must": [
        {"term": {
          "price": {
            "value": 4999
          }
        }}
      ]
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 9、多字段查询[multi_match]

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "iphone13 毫",
      "fields": ["title","description"]
    }
  }
}
注意: 字段类型分词,将查询条件分词之后进行查询改字段  如果该字段不分词就会将查询条件作为整体进行查询
1
2
3
4
5
6
7
8
9
10

# SpringBoot整合ElasticSearch

SpringBoot整合ElasticSearch核心是使用SpringDataES进行整合,整合方式非常简单,操作ES的客户端有两种。

# 1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
1
2
3
4

# 2、配置客户端

配置客户端有两种方式,下面这种是官方文档所写的方式。

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("172.16.91.10:9200")
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}
1
2
3
4
5
6
7
8
9
10
11

第二种的更为简便的配置文件配置方式:

spring:
  elasticsearch:
    uris: http://192.168.31.76:9200
1
2
3

# 客户端对象

  • ElasticsearchOperations:始终使用面向对象方式操作 ES
  • RestHighLevelClient:ElasticSearch官方提供的API,用Rest风格的JSON交互模式操作。

# ElasticsearchOperations方式

ElasticsearchOperations方式操作,就是常规的面向对象思想进行操作,使用起来非常简单。

# 1、核心概念回顾

  • 索引:用来存放相似文档集合,类似MySQL中的数据库

  • 映射:用来决定放入文档的每个字段以什么样方式录入到 ES 中 字段类型分词器,类似MySQL的表

  • 文档:可以被索引最小单元 json 数据格式

# 2、类与文档映射

@Document(indexName = "products", createIndex = true)
public class Product {
	@Id
    private Integer id;
    
    @Field(type = FieldType.Keyword)
    private String title;
    
    @Field(type = FieldType.Float)
    private Double price;
    
    @Field(type = FieldType.Text)
    private String description;
    
  	//get set ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

解释说明

代码 含义
@Document 标注当前类是一个文档,indexName是索引名称,createIndex设置当索引不存在时自动创建
@Id 设置该字段为文档Id
@Field 用来描述属性在ES中存储类型以及分词情况,type用来指定字段类型,analyzer指定该字段的分词器
Keyword 此类型不会进行分词
Text Text会进行分词,且可以通过analyzer指定该字段的分词器

# 3、保存/更新文档

class ApplicationTests {

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    /**
     * 索引一条文档,也就是保存,同时save方法也可用于更新
     * id存在则为更新,id不存在则为保存,不设置id统一都是保存
     */
    @Test
    public void testIndex() {
        Product product = new Product();
        product.setId(1);
        product.setTitle("小米13");
        product.setPrice(4899.6);
        product.setDescription("Xiaomi 联合徕卡深度研发,带来更具质感的专业影像,让你忘情地创作,记录美好一刻");
        elasticsearchOperations.save(product);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4、删除文档

@Test
public void delete() {
    Product product = new Product();
    product.setId(1);
    elasticsearchOperations.delete(product);
}
1
2
3
4
5
6

# 5、查询文档

@Test
public void testGet() {
  Product product = elasticsearchOperations.get("1", Product.class);
  System.out.println(product);
}
1
2
3
4
5

# 6、删除所有

@Test
public void testDeleteAll() {
  elasticsearchOperations.delete(Query.findAll(), Product.class);
}
1
2
3
4

# 7、查询所有

@Test
public void testFindAll() {
    SearchHits<Product> productSearchHits = elasticsearchOperations.search(Query.findAll(), Product.class);
    productSearchHits.forEach(productSearchHit -> {
        System.out.println("id: " + productSearchHit.getId());
        System.out.println("score: " + productSearchHit.getScore());
        Product product = productSearchHit.getContent();
        System.out.println("product: " + product);
    });
}
1
2
3
4
5
6
7
8
9
10

# RestHighLevelClient方式操作

Rest操作通常是在Kibana编写语句,然后将json格式的参数放入到java代码中。

# 1、创建索引映射

@Test
public void testCreateIndex() throws IOException {
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("fruit");
    createIndexRequest.mapping("{\n" +
                               "    \"properties\": {\n" +
                               "      \"title\":{\n" +
                               "        \"type\": \"keyword\"\n" +
                               "      },\n" +
                               "      \"price\":{\n" +
                               "        \"type\": \"double\"\n" +
                               "      },\n" +
                               "      \"created_at\":{\n" +
                               "        \"type\": \"date\"\n" +
                               "      },\n" +
                               "      \"description\":{\n" +
                               "        \"type\": \"text\"\n" +
                               "      }\n" +
                               "    }\n" +
                               "  }\n" , XContentType.JSON);
    // restHighLevelClient.indices()代表操作索引,create代表创建
    // 参数1是指定映射json结构,参数2是指定数据类型
    CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    System.out.println("创建状态:"+createIndexResponse.isAcknowledged());
    restHighLevelClient.close();
}
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

# 2、索引文档

@Test
public void testIndex() throws IOException {
  IndexRequest indexRequest = new IndexRequest("fruit");
  indexRequest.source("{\n" +
                      "          \"id\" : 1,\n" +
                      "          \"title\" : \"蓝月亮\",\n" +
                      "          \"price\" : 123.23,\n" +
                      "          \"description\" : \"这个洗衣液非常不错哦!\"\n" +
                      "        }",XContentType.JSON);
  IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  System.out.println(index.status());
}
1
2
3
4
5
6
7
8
9
10
11
12

# a、更新文档

@Test
public void testUpdate() throws IOException {
  UpdateRequest updateRequest = new UpdateRequest("fruit","qJ0R9XwBD3J1IW494-Om");
  updateRequest.doc("{\"title\":\"好月亮\"}",XContentType.JSON);
  UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
  System.out.println(update.status());
}
1
2
3
4
5
6
7

# b、删除文档

@Test
public void testDelete() throws IOException {
  DeleteRequest deleteRequest = new DeleteRequest("fruit","1");
  DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
  System.out.println(delete.status());
}
1
2
3
4
5
6

# c、基于 id 查询文档

@Test
public void testGet() throws IOException {
  GetRequest getRequest = new GetRequest("fruit","1");
  GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
  System.out.println(getResponse.getSourceAsString());
}
1
2
3
4
5
6

# d、查询所有

 @Test
public void testSearch() throws IOException {
  SearchRequest searchRequest = new SearchRequest("fruit");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.query(QueryBuilders.matchAllQuery());
  searchRequest.source(sourceBuilder);
  SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  //System.out.println(searchResponse.getHits().getTotalHits().value);
  SearchHit[] hits = searchResponse.getHits().getHits();
  for (SearchHit hit : hits) {
    System.out.println(hit.getSourceAsString());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# e、综合查询

 @Test
public void testSearch() throws IOException {
  SearchRequest searchRequest = new SearchRequest("fruit");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder
    .from(0)
    .size(2)
    .sort("price", SortOrder.DESC)
    .fetchSource(new String[]{"title"},new String[]{})
    .highlighter(new HighlightBuilder().field("description").requireFieldMatch(false).preTags("<span style='color:red;'>").postTags("</span>"))
    .query(QueryBuilders.termQuery("description","错"));
  searchRequest.source(sourceBuilder);
  SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  System.out.println("总条数: "+searchResponse.getHits().getTotalHits().value);
  SearchHit[] hits = searchResponse.getHits().getHits();
  for (SearchHit hit : hits) {
    System.out.println(hit.getSourceAsString());
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    highlightFields.forEach((k,v)-> System.out.println("key: "+k + " value: "+v.fragments()[0]));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# RestHighLevelClient之聚合查询

聚合查询是es除搜索功能外提供的针对es数据做统计分析的功能。聚合有助于根据搜索查询提供聚合数据。聚合查询是数据库中重要的功能特性,ES作为搜索引擎兼数据库,同样提供了强大的聚合分析能力。它基于查询条件来对数据进行分桶、计算的方法。有点类似于 SQL 中的 group by 再加一些函数方法的操作。

注意:text类型是不支持聚合的。

# 1、测试数据

# 创建索引 index 和映射 mapping
PUT /fruit
{
  "mappings": {
    "properties": {
      "title":{
        "type": "keyword"
      },
      "price":{
        "type":"double"
      },
      "description":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}
# 放入测试数据
PUT /fruit/_bulk
{"index":{}}
  {"title" : "面包","price" : 19.9,"description" : "小面包非常好吃"}
{"index":{}}
  {"title" : "旺仔牛奶","price" : 29.9,"description" : "非常好喝"}
{"index":{}}
  {"title" : "日本豆","price" : 19.9,"description" : "日本豆非常好吃"}
{"index":{}}
  {"title" : "小馒头","price" : 19.9,"description" : "小馒头非常好吃"}
{"index":{}}
  {"title" : "大辣片","price" : 39.9,"description" : "大辣片非常好吃"}
{"index":{}}
  {"title" : "透心凉","price" : 9.9,"description" : "透心凉非常好喝"}
{"index":{}}
  {"title" : "小浣熊","price" : 19.9,"description" : "童年的味道"}
{"index":{}}
  {"title" : "海苔","price" : 19.9,"description" : "海的味道"}
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

# 2、使用

# a、根据某个字段分组

GET /fruit/_search
{
  "query": {
    "term": {
      "description": {
        "value": "好吃"
      }
    }
  }, 
  "aggs": {
    "price_group": {
      "terms": {
        "field": "price"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# b、求最大值

GET /fruit/_search
{
  "aggs": {
    "price_max": {
      "max": {
        "field": "price"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# c、求最小值

GET /fruit/_search
{
  "aggs": {
    "price_min": {
      "min": {
        "field": "price"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# d、求平均值

GET /fruit/_search
{
  "aggs": {
    "price_agv": {
      "avg": {
        "field": "price"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# e、求和

GET /fruit/_search
{
  "aggs": {
    "price_sum": {
      "sum": {
        "field": "price"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# 3、整合应用

@Test
public void testAggsPrice() throws IOException {
  SearchRequest searchRequest = new SearchRequest("fruit");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.aggregation(AggregationBuilders.terms("group_price").field("price"));
  searchRequest.source(sourceBuilder);
  SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  Aggregations aggregations = searchResponse.getAggregations();
  ParsedDoubleTerms terms = aggregations.get("group_price");
  List<? extends Terms.Bucket> buckets = terms.getBuckets();
  for (Terms.Bucket bucket : buckets) {
    System.out.println(bucket.getKey() + ", "+ bucket.getDocCount());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 求不同名称的数量
@Test
public void testAggsTitle() throws IOException {
  SearchRequest searchRequest = new SearchRequest("fruit");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.aggregation(AggregationBuilders.terms("group_title").field("title"));
  searchRequest.source(sourceBuilder);
  SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  Aggregations aggregations = searchResponse.getAggregations();
  ParsedStringTerms terms = aggregations.get("group_title");
  List<? extends Terms.Bucket> buckets = terms.getBuckets();
  for (Terms.Bucket bucket : buckets) {
  	System.out.println(bucket.getKey() + ", "+ bucket.getDocCount());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 求和
@Test
public void testAggsSum() throws IOException {
  SearchRequest searchRequest = new SearchRequest("fruit");
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.aggregation(AggregationBuilders.sum("sum_price").field("price"));
  searchRequest.source(sourceBuilder);
  SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  ParsedSum parsedSum = searchResponse.getAggregations().get("sum_price");
  System.out.println(parsedSum.getValue());
}
1
2
3
4
5
6
7
8
9
10
11