目录
一.初识ElasticSearch
1.认识和安装
elasticsearch结合kibana、Logstash、Beats,是一整套技术栈,被叫做ELK。被广泛应用在日志数据分析、实时监控等领域。
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发 。官网地址:https://lucene.apache.org/
Lucene的优势:
- 易扩展
- 高性能(基于倒排索引)
Lucene的缺点:
- 只限于Java语言开发
- 学习曲线陡峭
- 不支持水平扩展
2004年Shay Banon基于Lucene开发了Compass
2010年Shay Banon 重写了Compass,取名为Elasticsearch。
官网地址: https://www.elastic.co/cn/
相比与lucene,elasticsearch具备下列优势:
- 支持分布式,可水平扩展
- 提供Restful接口,可被任何语言调用
安装:
下载地址 : Download Elasticsearch | Elastic
下载后解压后点击 \bin\elasticsearch.bat
测试是否安装成功,浏览器输入 https://localhost:9200/
登录名是elastic,登录密码,第一次启动时控制台是会打印密码的
此时我的密码是:yb+QdTjfl*eUY39pcQUY
启动成功之后,浏览器显示:
2. Kibana的安装与界面
Kibana 是 ES的可视化工具
下载地址: Download Kibana Free | Get Started Now | Elastic
下载下来,解压,解压后找到bin目录下的 kibana.bat,点击执行(执行前一定要启动es)
出现地址表示启动成功
访问地址 :http://localhost:5601/, 提示需要密钥
到es的bin目录下执行执行指令:elasticsearch-create-enrollment-token.bat --scope kibana
此时我的密码是:
eyJ2ZXIiOiI4LjE0LjAiLCJhZHIiOlsiMTkyLjE2OC4xLjE4Nzo5MjAwIl0sImZnciI6IjllNGY1OTE5OTBjMzU2NTQ2Mzc2NDA4MWQwZTMzMjlhYTYyMDc2YTJiNTA1OWEyNDc1YzRhYjAzMTQ2YzUxOWQiLCJrZXkiOiI4V2FLSkprQnpPcHhVOEF4WW1mMTpMZ1VxQzFOazVDdkVTVFotOHJDVlBnIn0=
复制生成的密钥到浏览器, 点击配置密钥。之后跳出验证码确认
把指令 kibana.bat的控制台显示出来的复制到浏览器
接下来输入es的账户及密码:
成功进入:
3.倒排索引
倒排索引:记录词条在哪个文档出现几次及在文档中的位置
什么是文档和词条?
- 每一条数据就是一个文档
- 对文档中的内容分词,得到的词语就是词条
什么是正向索引?
- 基于文档id创建索引。根据id查询快,但是查询词条时必须先找到文档,而后判断是否包含词条
什么是倒排索引?
- 对文档内容分词,对词条创建索引,并记录词条所在文档的id。 查询时先根据词条查询到文档id,而后根据文档id查询文档
4.IK分词器
分词器的作用是什么?
- 创建倒排索引时,对文档分词
- 用户搜索时,对输入的内容分词
默认分词器:标准分词器,对中文会一个个字切分,没有中文语义,形成不了有效的词条
例如:
语法说明:
POST:请求方式
/_analyze:请求路径,这里省略了http://192.168.12.168:9200,有kibana帮我们补充
请求参数,json风格: analyzer:分词器类型,这里是默认的standard分词器
text:要分词的内容
中文分词器(ik分词器):
- ik_smart(最大粒度:按照最长的词条进行分词;更适合搜索的时候)
- ik_max_word:(最细粒度:按照最短的词条进行分词,分的词条会比较多;更适合在创建索引的时候使用)
官网下载:https://release.infinilabs.com/analysis-ik/stable/
注意:IK分词器插件的版本要和ElasticSearch的版本一致
把解压后的文件放在ES 的所在目录下的的
plugins文件里然后重启ES
测试:
自定义词库:
找到ik 中的
config
文件夹的IKAnalyzer.cfg.xml文件并打开
在config文件创建ext.dic文件并添加词
重启ES并测试:
5.基础概念
二. 索引库操作
1. Mapping映射属性
2. 索引库操作
语法:
查看索引库语法: GET /索引库名 示例: GET /heima 删除索引库的语法: DELETE /索引库名 示例: DELETE /heima 索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下: PUT /索引库名/_mapping { "properties": { "新字段名":{ "type": "integer" } } } 示例: PUT /heima/_mapping { "properties": { "age":{ "type": "integer" } } }
三. 文档操作
1. 文档CRUD
查看文档语法: GET /索引库名/_doc/文档id 示例:GET /heima/_doc/1 删除索引库的语法:DELETE /索引库名/_doc/文档id 示例:DELETE /heima/_doc/1
修改:
2.批量处理
四.RestAPI
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
我们选择使用早期的JavaRestClient客户端来学习
官方文档地址:
https://www.elastic.co/guide/en/elasticsearch/client/index.html
1. 客户端初始化
添加依赖:
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.17.29</version> </dependency>
初始化RestHighLevelClient:
public class IndexTest { //es的操作客户端对象;执行任何与es有关的操作都需要 //说明接下来测试的每个方法都要先初始化该对象 private RestHighLevelClient client; //执行每个方法之前先执行的方法 @BeforeEach public void init() { client = new RestHighLevelClient( RestClient.builder(HttpHost.create("http://192.168.12.168:9200"))); } //测试cLient是否有效 @Test public void testGetConnection() { System.out.println(client); } //每次执行完之后要关闭client客户端的连接 @AfterEach public void close() throws Exception { client.close(); } }
运行测试类:
2.索引库操作
创建索引库:
//索引库名称 private static final String INDEX_NAME = "items"; //索引库映射结构 private static final String MAPPING_TEMPLATE="{\n" + " \"mappings\": {\n" + " \"properties\": {\n" + " \"id\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"name\":{\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\"\n" + " },\n" + " \"price\":{ \n" + " \"type\": \"integer\"\n" + " },\n" + " \"stock\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"image\":{\n" + " \"type\":\"keyword\",\n" + " \"index\": false\n" + " },\n" + " \"category\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"brand\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"sold\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"commentCount\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"isAD\":{\n" + " \"type\": \"boolean\"\n" + " },\n" + " \"updateTime\":{\n" + " \"type\": \"date\"\n" + " }\n" + " }\n" + " }\n" + "}"; //创建索引库 @Test public void createIndex() throws Exception { //创建索引库对象;--->创建索引--->创建索引的请求 CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME); //设置请求参数 request.source(MAPPING_TEMPLATE, XContentType.JSON); //发送请求 client.indices().create(request, RequestOptions.DEFAULT); }
删除索引库:
//测试删除索引库 @Test public void testDeleteIndex() throws Exception { //1、创建删除索引库请求对象 DeleteIndexRequest request = new DeleteIndexRequest(INDEX_NAME); //2、发送请求 client.indices().delete(request, RequestOptions.DEFAULT); }
判断索引库是否存在:
//测试查询索引库items是否存在 @Test public void testExistsIndex() throws Exception { //1、创建查询索引库请求对象 GetIndexRequest request = new GetIndexRequest(INDEX_NAME); //2、发送请求 boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); //3、打印结果 System.out.println(exists ? "索引库已经存在" : "索引库不存在"); }
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。XXX是Create、Get、Delete
- 准备请求参数(Create时需要)
- 发送请求。调用RestHighLevelClient#indices().xxx()方法 ,xxx是create、exists、delete
3.文档操作
文档操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxRequest。XXX是Index、Get、Update、Delete
- 准备参数(Index和Update时需要)
- 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是 index、get、update、delete
- 解析结果(Get时需要)
新增文档的JavaAPI如下:
新增商品:
public class DocumentTest { //索引库名称 private static final String INDEX_NAME = "items"; //es的操作客户端对象;执行任何与es有关的操作都需要 //说明接下来测试的每个方法都要先初始化该对象 private RestHighLevelClient client; //执行每个方法之前先执行的方法 @BeforeEach public void init() { client = new RestHighLevelClient( RestClient.builder(HttpHost.create("http://192.168.12.168:9200"))); } //每次执行完之后要关闭client客户端的连接 @AfterEach public void close() throws Exception { client.close(); } //根据商品id查询mysql数据库中的商品,并将该商品保存es @Test public void testCreateIndex() throws IOException{ //1、根据商品id查询mysql数据库中的商品 Item item = itemService.getById(317578L); //2、将Item对象转换为es可接受的对象ItemDoc ItemDoc itemDoc = BeanUtils.copyBean(item, ItemDoc.class); //3、创建文档的请求对象 IndexRequest request = new IndexRequest("items").id(itemDoc.getId().toString()); //4、设置请求参数 String jsonStr = JSONUtil.toJsonStr(itemDoc); request.source(jsonStr, XContentType.JSON); //3、发送请求 client.index(request, RequestOptions.DEFAULT); } }
查询:
查询文档包含查询和解析响应结果两部分。对应的JavaAPI如下:
修改:
删除:
4.批量操作
批处理代码流程与之前类似,只不过构建请求会用到一个名为BulkRequest的类
批处理的API示例:
由于商品数量达到数十万,因此不可能一次性全部导入。建议采用循环遍历方式,每次导入1000条左右的数据。
@Test public void importDocuments() throws IOException { //页号 int pageNo = 1; //页大小 int pageSize = 1000; while (true) { //1、根据页号、页大小(1000);每次查询1000条数据 //分页查询状态为1的商品数据 Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page<>(pageNo, pageSize)); List<Item> itemList = page.getRecords(); if (CollUtils.isEmpty(itemList)) { break; } //创建批量请求 BulkRequest bulkRequest = new BulkRequest(); for (Item item : itemList) { //2.将每条数据转换为ItemDoc:并设置到IndexRequest ItemDoc itemEsDTO = BeanUtils.copyBean(item, ItemDoc.class); //将ItemDoc转换为json字符串 String jsonStr = JSONUtil.toJsonStr(itemEsDTO); //创建request IndexRequest request = new IndexRequest("items").id(itemEsDTO.getId().toString()); //设置请求参数 request.source(jsonStr, XContentType.JSON); //添加到批量请求中 bulkRequest.add(request); } //3、发送批量请求,提交BuLkRequest client.bulk(bulkRequest, RequestOptions.DEFAULT); //4、继续分页查询;直到数据没有为止 pageNo++; } }
五.DSL查询
Elasticsearch的查询可以分为两大类:
- 叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
- 复合查询(Compound query clauses):以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。
在查询以后,还可以对查询的结果做处理,包括:
- 排序:按照1个或多个字段值做排序
- 分页:根据from和size做分页,类似MySQL
- 高亮:对搜索结果中的关键字添加特殊样式,使其更加醒目
- 聚合:对搜索结果做数据统计以形成报表
1.快速入门
基于DSL的查询语法如下:
GET /indexName/_search { "query": { "查询类型": { "查询条件": "条件值" } } } // 查询所有 GET /indexName/_search { "query": { "match_all": {} } }
2.叶子查询
叶子查询还可以进一步细分,常见的有:
- 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如: match_query multi_match_query
- 精确查询:不对用户输入内容分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。例如: ids range term
(1)全文检索
match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:
GET /indexName/_search { "query": { "match": { "FIELD": "TEXT" } } } #单个字符中分词查询 GET /items/_search { "query": { "match": { "name": "脱脂牛奶" } } }
multi_match:与match查询类似,只不过允许同时查询多个字段,语法:
GET /indexName/_search { "query": { "multi_match": { "query": "TEXT", "fields": ["FIELD1", " FIELD12"] } } } #多个字段中分词搜索 GET /indexName/_search { "query": { "multi_match": { "query": "手机", "fields": ["name", "category"] } } }
match和multi_match的区别是什么?
- match:根据一个字段查询
- multi_match:根据多个字段查询,参与查询字段越多,查询性能越差
(2)精确查询
精确查询,英文是Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。
因此推荐查找keyword、数值、日期、boolean类型的字段。例如id、price、城市、地名、人名等作为一个整体才有含义的字段。
词条查询:
//term查询 GET /indexName/_search { "query": { "term": { "FIELD": { "value": "VALUE" } } } } 例如: GET /items/_search { "query": { "term": { "brand": { "value": "锤子" } } } }
范围查询:
// range查询 GET /indexName/_search { "query": { "range": { "FIELD": { "gte": 10, "lte": 20 } } } } 例如: GET /items/_search { "query": { "range": { "price": { "gte": 10000, "lte": 20000 } } } }
精确查询常见的有哪些?
- term查询:根据词条精确匹配,一般搜索keyword类型、数 值类型、布尔类型、日期类型字段
- range查询:根据数值范围查询,可以是数值、日期的范围
3.复合查询
复合查询大致可以分为两类:
- 第一类:基于逻辑运算组合叶子查询,实现组合条件,例如 bool
- 第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如: function_score dis_max
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
例如:我们要搜索"智能手机",但品牌必须是华为,价格必须是900~1599
GET /items/_search { "query": { "bool": { "must": [ {"match": {"name": "手机"}} ], "filter": [ {"term": {"brand": { "value": "华为" }}}, {"range": {"price": {"gte": 90000, "lt": 159900}}} ] } } }
4.排序和分页
排序:
elasticsearch支持对搜索结果排序 ,默认是根据相关度算分(_score)来排序,也可以指定字段排序。可以排序字段类 型有:keyword类型、数值类型、地理坐标类型、日期类型等。
语法:
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ {"排序字段": {"order": "排序方式asc和desc"}} ] }
例如:
搜索商品,按照销量排序,销量一样则按照价格升序
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ {"sold":{"order": "desc"}}, {"price":{"order":"asc"}} ] }
分页:
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
语法:
GET /items/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ {"price": "asc"} ] }
例如:搜索商品,查询出销量排名前10的商品,销量一样时按照价格升序
GET /indexName/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ {"sold":{"order": "desc"}}, {"price":{"order":"asc"}} ] }
深度分页问题:
elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。
比如一个索引库中有100000条数据,分别存储到4个分片,每个分片25000条数据。现在每页查询10条,查询第99页。那么分页查询的条件如下:
GET /items/_search { "from": 990, // 从第990条开始查询 "size": 10, // 每页查询10条 "sort": [ { "price": "asc" } ] }
从语句来分析,要查询第990~1000名的数据。
从实现思路来分析,肯定是将所有数据排序,找出前1000名,截取其中的990~1000的部分。但问题来了,我们如何才能找到所有数据中的前1000名呢?
要知道每一片的数据都不一样,第1片上的第900~1000,在另1个节点上并不一定依然是900~1000名。所以我们只能在每一个分片上都找出排名前1000的数据,然后汇总到一起,重新排序,才能找出整个索引库中真正的前1000名,此时截取990~1000的数据即可。
如图:
试想一下,假如我们现在要查询的是第999页数据呢,是不是要找第9990~10000的数据,那岂不是需要把每个分片中的前10000名数据都查询出来,汇总在一起,在内存中排序?如果查询的分页深度更深呢,需要一次检索的数据岂不是更多?
由此可知,当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力。
因此elasticsearch会禁止
from+ size
超过10000的请求。针对深度分页,elasticsearch提供了两种解决方案:
详情见文档:
search after
:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。(意思是要记录上一次查询的最后一条记录的排序值,然后携带到下一次查询。)官方推荐使用的方式。
scroll
:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用https://www.elastic.co/guide/en/elasticsearch/reference/7.12/paginate-search-results.html
总结:大多数情况下,我们采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限制。例如百度最多支持77页,每页不足20条。京东最多100页,每页最多60条。
因此,一般我们采用限制分页深度的方式即可,无需实现深度分页
5.高亮显示
高亮显示就是在搜索结果中把搜索关键字突出显示
语法:
GET /{索引库名}/_search { "query": { "match": { "搜索字段": "搜索关键字" } }, "highlight": { "fields": { //指定要高亮的字段 "高亮字段名称": { "pre_tags": "<em>", //高亮的前置标签 "post_tags": "</em>" //高亮的前置标签 } } } }
注意:
搜索必须有查询条件,而且是全文检索类型的查询条件,例如
match
参与高亮的字段必须是
text
类型的字段默认情况下参与高亮的字段要与搜索字段一致,除非添加:
required_field_match=false
例如:
GET /items/_search { "query": { "match": { "name": "手机" } }, "highlight": { "fields": { //指定要高亮的字段 "name": { "pre_tags": "<em cloor='red'>", //高亮的前置标签 "post_tags": "</em>" //高亮的前置标签 } } } }
六. RestClient查询
1. 快速入门
数据搜索的Java代码我们分为两部分: 构建请求参数和解析查询结果
@Test public void testMatchAll() throws IOException { //创建请求对象 SearchRequest searchRequest = new SearchRequest(INDEX_NAME); //设置请求参数 searchRequest.source().query(QueryBuilders.matchAllQuery()); //发送请求并获取结果 SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); //解析响应结果 SearchHits searchHits = searchResponse.getHits(); //符合本次搜索的总文档 数(最多10000条) long total = searchHits.getTotalHits().value; System.out.println("本次搜索命中的总文档数:"+total); SearchHit[] hits = searchHits.getHits(); if(hits!= null&&hits.length>0){ for (SearchHit hit:hits){ //获取原文的json字符串 String jsonStr = hit.getSourceAsString(); ItemDoc itemDoc = JSONUtil.toBean(jsonStr,ItemDoc.class); System.out.println(itemDoc); } } }
查询的基本步骤是:
1. 创建SearchRequest对象
2. 准备Request.source(),也就是DSL。
① QueryBuilders来构建查询条件
② 传入Request.source() 的query() 方法
3. 发送请求,得到结果
4. 解析结果(参考JSON结果,从外到内,逐层解析)
2.构建查询条件
全文检索的查询条件构造API如下:
// 单字段查询 QueryBuilders.matchQuery("name", "脱脂牛奶"); // 多字段查询 QueryBuilders.multiMatchQuery("脱脂牛奶", "name", "category");
精确查询的查询条件构造API如下:
// 词条查询 QueryBuilders.termQuery("category", "牛奶"); // 范围查询 QueryBuilders.rangeQuery("price").gte(100).lte(150);
布尔查询的查询条件构造API如下:
// 创建布尔查询 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); // 添加must条件 boolQuery.must(QueryBuilders.termQuery("brand", "华为")); // 添加filter条件 boolQuery.filter(QueryBuilders.rangeQuery("price").lte(2500));
排序和分页:
ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下:
// 查询 request.source().query(QueryBuilders.matchAllQuery()); // 分页 request.source().from(0).size(5); // 价格排序 request.source().sort("price", SortOrder.ASC);
高亮显示:
request.source().highlighter(SearchSourceBuilder.highlight() .field("name") .preTags("<em>") .postTags("</em>"));
代码如下:
//测试高亮 @Test public void testHighlight() throws IOException { //1、创建搜索请求 SearchRequest request = new SearchRequest(INDEX_NAME); //2、设置查询参数 request.source().query(QueryBuilders.matchQuery("name", "华为")); //设置高亮 request.source().highlighter(SearchSourceBuilder.highlight() .field("name") .preTags("<em>") .postTags("</em>") ); //3、发送请求获取响应结果 SearchResponse response = client.search(request, RequestOptions.DEFAULT); //4、解析响应结果 handleResponse(response); } private static void handleResponse(SearchResponse response) { System.out.println("共搜索到 " + response.getHits().getTotalHits().value + " 条数据"); //获取查询数组 SearchHit[] hits = response.getHits().getHits(); for (SearchHit hit : hits) { //获取_source(原始json数据) String jsonStr = hit.getSourceAsString(); ItemDoc itemDoc = JSONUtil.toBean(jsonStr, ItemDoc.class); //解析高亮结果 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); if (CollUtils.isNotEmpty(highlightFields)) { HighlightField highlightField = highlightFields.get("name"); if (highlightField != null) { String highlightStr = highlightField.getFragments()[0].string(); itemDoc.setName(highlightStr); } } System.out.println(itemDoc); } }
3.数据聚合
聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:
桶(Bucket)聚合:用来对文档做分组
TermAggregation:按照文档字段值分组
Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
Avg:求平均值
Max:求最大值
Min:求最小值
Stats:同时求max、min、avg、sum等
管道(pipeline)聚合:其它聚合的结果为基础做聚合
注意:参与聚合的字段必须是Keyword、数值、日期、布尔的类型的字段
(1)DSL聚合
我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合。
GET /items/_search { "size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果 "aggs": { // 定义聚合 "cateAgg": { //给聚合起个名字 “terms”: { // 聚合的类型,按照品牌值聚合,所以选择term "field": “category", // 参与聚合的字段 "size": 20 // 希望获取的聚合结果数量 } } } }
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可
GET /items/_search { "query": { "bool": { "filter": [ { "term": {"category": "手机"}}, { "range": { "price": { "gte": 300000}}} ] } }, "size": 0, "aggs": { "brand_agg": { "terms": { "field": "brand", "size": 20 } } } }
除了对数据分组(Bucket)以外,我们还可以对每个Bucket内的数据进一步做数据计算和统计。例如:我想知道手机有哪些品牌,每个品牌的价格最小值、最大值、平均值。
GET /items/_search { "query": { "term": {"category": "手机"} }, "size": 0, "aggs": { "brand_agg": { "terms": { "field": "brand" }, "aggs": { "stats_metric": { "stats": { "field": "price" } } } } } }
aggs代表聚合,与query同级,此时query的作用是?
限定聚合的的文档范围聚合必须的三要素:
聚合名称聚合类型聚合字段聚合可配置属性有:
size :指定聚合结果数量field :指定聚合字段
(2)RestClient聚合
//聚合统计 @Test public void testAggs() throws IOException { SearchRequest searchRequest=new SearchRequest(INDEX_NAME); //不返回文件 searchRequest.source().size(0); //设置品牌的聚合统计 searchRequest.source().aggregation( AggregationBuilders.terms("brand_agg").field("brand").size(20) .subAggregation( AggregationBuilders.stats(name:"stat_metric").field("price") ) ); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); //解析聚合统计结果 Aggregations aggregations = searchResponse.getAggregations(); Terms brandAgg = aggregations.get("brand_agg"); if(brandAgg!=null){ List<? extends Terms.Bucket> buckets = brandAgg.getBuckets(); for (Terms.Bucket bucket : buckets){ System.out.println(bucket.getKeyAsString()+"."+bucket.getDocCount()); Aggregations statAgg=bucket.getAggregations(); Stats statMetric=statAgg.get("stat_metric"); System.out.println("平均值:"+statMetric.getAvg()); System.out.println("最大值:"+statMetric.getMax()); System.out.println("最小值:"+statMetric.getMin()); System.out.println("总和:"+statMetric.getSum()); } } }