Elasticsearch安裝?+?CRUD?+?查詢實(shí)戰(zhàn)指南
一、Elasticsearch 是什么??
Elasticsearch(簡(jiǎn)稱 ES) 是基于 Lucene 的分布式全文搜索引擎,支持近實(shí)時(shí)搜索、海量數(shù)據(jù)存儲(chǔ)、高可用集群、RESTful API,廣泛用于日志分析、商品搜索、數(shù)據(jù)可視化等場(chǎng)景。?
在搜索時(shí)為什么我們要用 Elasticsearch,而不是 MySQL 模糊匹配?
在實(shí)際業(yè)務(wù)中,商品搜索、日志檢索、全文搜索 這些場(chǎng)景,MySQL 根本扛不住。
1. MySQL 的like '%關(guān)鍵詞%'為什么慢?
- 無(wú)法走索引,必須全表掃描
- 數(shù)據(jù)量越大,速度越慢
- 不支持分詞,不支持相關(guān)性打分
- 不支持高亮、同義詞、拼音搜索
簡(jiǎn)單說(shuō):數(shù)據(jù)量一大,like 查詢直接拖垮數(shù)據(jù)庫(kù)。
2. Elasticsearch 為什么快?—— 倒排索引原理
ES 快的核心原因只有一個(gè):它用了 倒排索引(Inverted Index)。
我用最簡(jiǎn)單的方式講清楚:
正常數(shù)據(jù)庫(kù)(正排索引)
文檔1 → 我喜歡華為手機(jī)
文檔2 → 華為手機(jī)信號(hào)好
文檔3 → 蘋果手機(jī)也不錯(cuò)
倒排索引(關(guān)鍵詞 → 文檔)
華為 → 文檔1、文檔2
手機(jī) → 文檔1、文檔2、文檔3
蘋果 → 文檔3 喜歡 → 文檔1
當(dāng)你搜索:“華為”
ES 不需要掃全表,直接查倒排索引:華為 → 文檔 1、文檔 2直接返回,毫秒級(jí)響應(yīng)。
3. 倒排索引三大優(yōu)勢(shì)
- 檢索極快:不需要全表掃描
- 支持分詞:把一句話切成詞語(yǔ)
- 相關(guān)性打分:最匹配的排前面
MySQL 適合存數(shù)據(jù),Elasticsearch 適合搜數(shù)據(jù)。
MySQL 用 B+ 樹,適合精確查詢。
Elasticsearch 用倒排索引,適合全文檢索。
Elasticsearch核心概念?
- 索引(Index):對(duì)應(yīng) MySQL 的數(shù)據(jù)庫(kù)?
- 文檔(Document):對(duì)應(yīng) MySQL 的行數(shù)據(jù)?
- 映射(Mapping):對(duì)應(yīng) MySQL 的表結(jié)構(gòu)?
- 分片 / 副本:保證分布式存儲(chǔ)與高可用
文檔數(shù)據(jù)會(huì)被序列化為json格式后存儲(chǔ)在elasticsearch
我們統(tǒng)一的把mysql與elasticsearch的概念做一下對(duì)比

Kibana是elastic公司提供的用于操作Elasticsearch的可視化控制臺(tái)。它的功能非常強(qiáng)大,包括:
- 對(duì)Elasticsearch數(shù)據(jù)的搜索、展示
- 對(duì)Elasticsearch數(shù)據(jù)的統(tǒng)計(jì)、聚合,并形成圖形化報(bào)表、圖形
- 對(duì)Elasticsearch的集群狀態(tài)監(jiān)控
- 它還提供了一個(gè)開(kāi)發(fā)控制臺(tái)(DevTools),在其中對(duì)Elasticsearch的Restful的API接口提供了語(yǔ)法提示
elasticsearch安裝
通過(guò)下面的Docker命令即可安裝單機(jī)版本的elasticsearch:
docker run -d \ --name es \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ -e "discovery.type=single-node" \ -v es-data:/usr/share/elasticsearch/data \ -v es-plugins:/usr/share/elasticsearch/plugins \ --privileged \ --network hm-net \ -p 9200:9200 \ -p 9300:9300 \ elasticsearch:7.12.1
訪問(wèn)9200端口,即可看到響應(yīng)的Elasticsearch服務(wù)的基本信息:

安裝kibana
docker run -d \ --name kibana \ -e ELASTICSEARCH_HOSTS=http://es:9200 \ --network=hm-net \ -p 5601:5601 \ kibana:7.12.1
訪問(wèn)5601端口,即可看到控制臺(tái)頁(yè)面:


ik分詞器
Elasticsearch的關(guān)鍵就是倒排索引,而倒排索引依賴于對(duì)文檔內(nèi)容的分詞,而分詞則需要高效、精準(zhǔn)的分詞算法,IK分詞器就是這樣一個(gè)中文分詞算法。
安裝:
docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
IK分詞器包含兩種模式:
ik_smart:智能語(yǔ)義切分ik_max_word:最細(xì)粒度切分
詞典:隨著互聯(lián)網(wǎng)的發(fā)展出現(xiàn)了很多新的詞語(yǔ),在原有的詞匯列表中并不存在,IK分詞器無(wú)法對(duì)這些詞匯分詞,要想正確分詞,IK分詞器的詞庫(kù)也需要不斷的更新,IK分詞器提供了擴(kuò)展詞匯的功能
在分詞器的配置目錄下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典 *** 添加擴(kuò)展詞典-->
<entry key="ext_dict">ext.dic</entry>
</properties>改目錄下目錄新建一個(gè) ext.dic,然后在里面添加新的詞匯,重啟后ik分詞器就能正確識(shí)別ext.dic的這些單詞
CRUD操作:
由于Elasticsearch采用的是Restful風(fēng)格的API,因此其請(qǐng)求方式和路徑相對(duì)都比較規(guī)范,而且請(qǐng)求參數(shù)也都采用JSON風(fēng)格。
我們直接基于Kibana的DevTools來(lái)編寫請(qǐng)求做測(cè)試,由于有語(yǔ)法提示,會(huì)非常方便。
.創(chuàng)建索引庫(kù)和映射
基本語(yǔ)法:
- 請(qǐng)求方式:
PUT - 請(qǐng)求路徑:
/索引庫(kù)名,可以自定義 - 請(qǐng)求參數(shù):
mapping映射
PUT /索引庫(kù)名稱
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}查詢索引庫(kù)
基本語(yǔ)法:
- 請(qǐng)求方式:GET
- 請(qǐng)求路徑:/索引庫(kù)名
- 請(qǐng)求參數(shù):無(wú)
比如:GET /item
修改索引庫(kù)
倒排索引結(jié)構(gòu)雖然不復(fù)雜,但是一旦數(shù)據(jù)結(jié)構(gòu)改變(比如改變了分詞器),就需要重新創(chuàng)建倒排索引,這簡(jiǎn)直是災(zāi)難。因此索引庫(kù)一旦創(chuàng)建,無(wú)法修改mapping。
雖然無(wú)法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因?yàn)椴粫?huì)對(duì)倒排索引產(chǎn)生影響。因此修改索引庫(kù)能做的就是向索引庫(kù)中添加新字段,或者更新索引庫(kù)的基礎(chǔ)屬性。
語(yǔ)法:
PUT /索引庫(kù)名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}刪除索引庫(kù)
語(yǔ)法:
- 請(qǐng)求方式:DELETE
- 請(qǐng)求路徑:/索引庫(kù)名
- 請(qǐng)求參數(shù):無(wú)
文檔操作
有了索引庫(kù),接下來(lái)就可以向索引庫(kù)中添加數(shù)據(jù)了。
4.1 新增文檔(指定 ID)?
PUT /product/_doc/1?
{?
"id": 1,?
"name": "華為Mate70",?
"price": 5999,?
"category": "手機(jī)"?
}?新增文檔(自動(dòng) ID)
POST /product/_doc?
{?
"id": 2,?
"name": "小米14",?
"price": 4299,?
"category": "手機(jī)"?
}4.3 查詢文檔(根據(jù) ID)
GET /product/_doc/1
全量更新文檔(相當(dāng)于覆蓋原文檔)
PUT /product/_doc/1
{
"id": 1,
"name": "華為Mate70 Pro",
"price": 6999,
"category": "手機(jī)"
}局部更新文檔(推薦)
POST /product/_update/1
{
"doc": {
"price": 6799
}
}刪除文檔
DELETE /product/_doc/1
批處理
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }index代表新增操作_index:指定索引庫(kù)名_id指定要操作的文檔id{ "field1" : "value1" }:則是要新增的文檔內(nèi)容
delete代表刪除操作_index:指定索引庫(kù)名_id指定要操作的文檔id
update代表更新操作_index:指定索引庫(kù)名_id指定要操作的文檔id{ "doc" : {"field2" : "value2"} }:要更新的文檔字段
總結(jié)
創(chuàng)建文檔:
POST /{索引庫(kù)名}/_doc/文檔id { json文檔 }查詢文檔:
GET /{索引庫(kù)名}/_doc/文檔id刪除文檔:
DELETE /{索引庫(kù)名}/_doc/文檔id修改文檔:
全量修改:
PUT /{索引庫(kù)名}/_doc/文檔id { json文檔 }局部修改:
POST /{索引庫(kù)名}/_update/文檔id { "doc":{字段}}
在java中訪問(wèn)客戶端
引入依賴
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>因?yàn)镾pringBoot默認(rèn)的ES版本是7.17.10,所以我們需要覆蓋默認(rèn)的ES版本
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));package com.hmall.item.es;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class IndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@Test
void testConnect() {
System.out.println(client);
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}RestClient操作索引庫(kù)

代碼分為三步:
1)創(chuàng)建Request對(duì)象。
因?yàn)槭莿?chuàng)建索引庫(kù)的操作,因此Request是
CreateIndexRequest。
2)添加請(qǐng)求參數(shù)
其實(shí)就是Json格式的Mapping映射參數(shù)。因?yàn)閖son字符串很長(zhǎng),這里是定義了靜態(tài)字符串常量
MAPPING_TEMPLATE,讓代碼看起來(lái)更加優(yōu)雅。
3)發(fā)送請(qǐng)求
client.indices()方法的返回值是IndicesClient類型,封裝了所有與索引庫(kù)操作有關(guān)的方法。例如創(chuàng)建索引、刪除索引、判斷索引是否存在等
RestClient操作文檔
新增文檔
// 1.準(zhǔn)備Request對(duì)象
IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
// 2.準(zhǔn)備Json文檔
request.source(doc, XContentType.JSON);
// 3.發(fā)送請(qǐng)求
client.index(request, RequestOptions.DEFAULT);1)創(chuàng)建Request對(duì)象,這里是
IndexRequest,因?yàn)樘砑游臋n就是創(chuàng)建倒排索引的過(guò)程2)準(zhǔn)備請(qǐng)求參數(shù),本例中就是Json文檔
3)發(fā)送請(qǐng)求
查詢文檔

刪除文檔
@Test
void testDeleteDocument() throws IOException {
// 1.準(zhǔn)備Request,兩個(gè)參數(shù),第一個(gè)是索引庫(kù)名,第二個(gè)是文檔id
DeleteRequest request = new DeleteRequest("item", "100002644680");
// 2.發(fā)送請(qǐng)求
client.delete(request, RequestOptions.DEFAULT);
}修改文檔
修改我們講過(guò)兩種方式:
全量修改:本質(zhì)是先根據(jù)id刪除,再新增
局部修改:修改文檔中的指定字段值
在RestClient的API中,全量修改與新增的API完全一致,判斷依據(jù)是ID:
如果新增時(shí),ID已經(jīng)存在,則修改
如果新增時(shí),ID不存在,則新增
這里不再贅述,我們主要關(guān)注局部修改的API即可。

.批量導(dǎo)入文檔
@Test
void testBulk() throws IOException {
// 1.創(chuàng)建Request
BulkRequest request = new BulkRequest();
// 2.準(zhǔn)備請(qǐng)求參數(shù)
request.add(new IndexRequest("items").id("1").source("json doc1", XContentType.JSON));
request.add(new IndexRequest("items").id("2").source("json doc2", XContentType.JSON));
// 3.發(fā)送請(qǐng)求
client.bulk(request, RequestOptions.DEFAULT);
}總結(jié)
初始化
RestHighLevelClient創(chuàng)建XxxRequest。
XXX是
Index、Get、Update、Delete、Bulk
準(zhǔn)備參數(shù)(
Index、Update、Bulk時(shí)需要)發(fā)送請(qǐng)求。
調(diào)用
RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
解析結(jié)果(
Get時(shí)需要)
DSL查詢
在 Elasticsearch 中,所有復(fù)雜查詢都是通過(guò) DSL(Domain Specific Language) 來(lái)實(shí)現(xiàn)的。簡(jiǎn)單說(shuō):DSL = 用 JSON 寫查詢語(yǔ)句
所有查詢都分為兩類:
(1)查詢上下文(Query Context)
會(huì)計(jì)算相關(guān)性分?jǐn)?shù)(_score)用于:全文搜索、模糊匹配例:match、match_phrase、multi_match
(2)過(guò)濾上下文(Filter Context)
不計(jì)算分?jǐn)?shù),只判斷 是 / 否速度極快,會(huì)自動(dòng)緩存用于:精確匹配、范圍、布爾過(guò)濾例:term、range、bool filter
最常用 DSL 查詢
match 全文檢索
對(duì)字段分詞后匹配
{
"query": {
"match": {
"name": "拉桿箱"
}
}
}查詢結(jié)果一般格式

term 精確匹配(不分詞)
用于 keyword、數(shù)字、布爾
{
"query": {
"term": {
"category": "手機(jī)"
}
}
}range 范圍查詢
{
"query": {
"range": {
"price": {
"gte": 3000,
"lte": 8000
}
}
}
}搜索的關(guān)鍵字高亮
GET /{索引庫(kù)名}/_search
{
"query": {
"match": {
"搜索字段": "搜索關(guān)鍵字"
}
},
"highlight": {
"fields": {
"高亮字段名稱": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
}
}
}bool :組合查詢包含四個(gè)子句:
- must:必須滿足(算分)
- should:滿足任意一個(gè)
- must_not:必須不滿足
- filter:必須滿足(過(guò)濾,不計(jì)分)
示例
{
"query": {
"bool": {
"must": [
{ "match": { "name": "手機(jī)" } }
],
"filter": [
{ "range": { "price": { "gte": 2000 } } }
]
}
}
}分頁(yè)、排序、指定返回字段
{
"from": 0,
"size": 10,
"_source": ["name", "price"],
"sort": [
{ "price": { "order": "desc" } }
],
"query": {
"match_all": {}
}
}總結(jié)
查詢的DSL是一個(gè)大的JSON對(duì)象,包含下列屬性:
query:查詢條件from和size:分頁(yè)條件sort:排序條件highlight:高亮條件
Java中RestClient查詢

第一步,創(chuàng)建
SearchRequest對(duì)象,指定索引庫(kù)名第二步,利用
request.source()構(gòu)建DSL,DSL中可以包含查詢、分頁(yè)、排序、高亮等query():代表查詢條件,利用QueryBuilders.matchAllQuery()構(gòu)建一個(gè)match_all查詢的DSL
第三步,利用
client.search()發(fā)送請(qǐng)求,得到響應(yīng)
request.source(),它構(gòu)建的就是DSL中的完整JSON參數(shù)。其中包含了query、sort、from、size、highlight等所有功能

QueryBuilders,其中包含了葉子查詢、復(fù)合查詢

解析響應(yīng)結(jié)果

elasticsearch返回的結(jié)果是一個(gè)JSON字符串,結(jié)構(gòu)包含:
hits:命中的結(jié)果total:總條數(shù),其中的value是具體的總條數(shù)值max_score:所有結(jié)果中得分最高的文檔的相關(guān)性算分hits:搜索結(jié)果的文檔數(shù)組,其中的每個(gè)文檔都是一個(gè)json對(duì)象_source:文檔中的原始數(shù)據(jù),也是json對(duì)象
因此,我們解析響應(yīng)結(jié)果,就是逐層解析JSON字符串,流程如下:
SearchHits:通過(guò)response.getHits()獲取,就是JSON中的最外層的hits,代表命中的結(jié)果SearchHits#getTotalHits().value:獲取總條數(shù)信息SearchHits#getHits():獲取SearchHit數(shù)組,也就是文檔數(shù)組SearchHit#getSourceAsString():獲取文檔結(jié)果中的_source,也就是原始的json文檔數(shù)據(jù)
葉子查詢
所有的查詢條件都是由QueryBuilders來(lái)構(gòu)建的,葉子查詢也不例外。因此整套代碼中變化的部分僅僅是query條件構(gòu)造的方式,其它不動(dòng)。
match查詢:
@Test
void testMatch() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
request.source().query(QueryBuilders.matchQuery("name", "脫脂牛奶"));
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}multi_match查詢
@Test
void testMultiMatch() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
request.source().query(QueryBuilders.multiMatchQuery("脫脂牛奶", "name", "category"));
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}還有range查詢:
@Test
void testRange() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
request.source().query(QueryBuilders.rangeQuery("price").gte(10000).lte(30000));
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}term查詢:
@Test
void testTerm() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
request.source().query(QueryBuilders.termQuery("brand", "華為"));
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}復(fù)合查詢
復(fù)合查詢也是由QueryBuilders來(lái)構(gòu)建,我們以bool查詢?yōu)槔?,DSL和JavaAPI的對(duì)比如圖:

@Test
void testBool() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
// 2.1.準(zhǔn)備bool查詢
BoolQueryBuilder bool = QueryBuilders.boolQuery();
// 2.2.關(guān)鍵字搜索
bool.must(QueryBuilders.matchQuery("name", "脫脂牛奶"));
// 2.3.品牌過(guò)濾
bool.filter(QueryBuilders.termQuery("brand", "德亞"));
// 2.4.價(jià)格過(guò)濾
bool.filter(QueryBuilders.rangeQuery("price").lte(30000));
request.source().query(bool);
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}排序和分頁(yè)

@Test
void testPageAndSort() throws IOException {
int pageNo = 1, pageSize = 5;
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
// 2.1.搜索條件參數(shù)
request.source().query(QueryBuilders.matchQuery("name", "脫脂牛奶"));
// 2.2.排序參數(shù)
request.source().sort("price", SortOrder.ASC);
// 2.3.分頁(yè)參數(shù)
request.source().from((pageNo - 1) * pageSize).size(pageSize);
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}高亮
@Test
void testHighlight() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.組織請(qǐng)求參數(shù)
// 2.1.query條件
request.source().query(QueryBuilders.matchQuery("name", "脫脂牛奶"));
// 2.2.高亮條件
request.source().highlighter(
SearchSourceBuilder.highlight()
.field("name")
.preTags("<em>")
.postTags("</em>")
);
// 3.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
// 1.獲取總條數(shù)
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "條數(shù)據(jù)");
// 2.遍歷結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 3.得到_source,也就是原始json文檔
String source = hit.getSourceAsString();
// 4.反序列化
ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
// 5.獲取高亮結(jié)果
Map<String, HighlightField> hfs = hit.getHighlightFields();
if (CollUtils.isNotEmpty(hfs)) {
// 5.1.有高亮結(jié)果,獲取name的高亮結(jié)果
HighlightField hf = hfs.get("name");
if (hf != null) {
// 5.2.獲取第一個(gè)高亮結(jié)果片段,就是商品名稱的高亮值
String hfName = hf.getFragments()[0].string();
item.setName(hfName);
}
}
System.out.println(item);
}
}數(shù)據(jù)聚合
聚合 = 對(duì)數(shù)據(jù)進(jìn)行統(tǒng)計(jì)、分組、求和、平均值、最大值、最小值、計(jì)數(shù)。
你可以把它理解為:MySQL 的 group by + sum + avg + count 超級(jí)加強(qiáng)版。
聚合的基本結(jié)構(gòu)
{
"size": 0, // 不返回原始數(shù)據(jù),只返回統(tǒng)計(jì)結(jié)果
"aggs": { // 聚合固定寫 aggs
"自定義名稱": {
"聚合類型": {
"field": "字段名"
}
}
}
}聚合常見(jiàn)的有三類:
桶(
Bucket)聚合:用來(lái)對(duì)文檔做分組TermAggregation:按照文檔字段值分組,例如按照品牌值分組、按照國(guó)家分組Date Histogram:按照日期階梯分組,例如一周為一組,或者一月為一組
度量(
Metric)聚合:用以計(jì)算一些值,比如:最大值、最小值、平均值等Avg:求平均值Max:求最大值Min:求最小值Stats:同時(shí)求max、min、avg、sum等
管道(
pipeline)聚合:其它聚合的結(jié)果為基礎(chǔ)做進(jìn)一步運(yùn)算
注意:參加聚合的字段必須是keyword、日期、數(shù)值、布爾類型
.Bucket聚合
例如我們要統(tǒng)計(jì)所有商品中共有哪些商品分類,其實(shí)就是以分類(category)字段對(duì)數(shù)據(jù)分組。category值一樣的放在同一組,屬于Bucket聚合中的Term聚合。
GET /items/_search
{
"size": 0,
"aggs": {
"category_agg": {
"terms": {
"field": "category",
"size": 20
}
}
}
}size:設(shè)置size為0,就是每頁(yè)查0條,則結(jié)果中就不包含文檔,只包含聚合aggs:定義聚合category_agg:聚合名稱,自定義,但不能重復(fù)terms:聚合的類型,按分類聚合,所以用termfield:參與聚合的字段名稱size:希望返回的聚合結(jié)果的最大數(shù)量
查詢的結(jié)果:

嵌套聚合
先按分類分組 → 再求每組的平均價(jià)格
GET /product/_search
{
"size": 0,
"aggs": {
"group_by_category": {
"terms": {
"field": "category"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}結(jié)果
手機(jī) → 平均價(jià)格 5800
電腦 → 平均價(jià)格 7200
按價(jià)格區(qū)間統(tǒng)計(jì)(range 聚合)
電商最常用:價(jià)格分段統(tǒng)計(jì)
GET /product/_search
{
"size": 0,
"aggs": {
"price_range": {
"range": {
"field": "price",
"ranges": [
{ "to": 3000 },
{ "from": 3000, "to": 6000 },
{ "from": 6000 }
]
}
}
}
}對(duì)搜索結(jié)果聚合。
例如,我想知道價(jià)格高于3000元的手機(jī)品牌有哪些
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手機(jī)"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}求平均值(avg)
GET /product/_search
{
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}求和(sum)
GET /product/_search
{
"size": 0,
"aggs": {
"total_price": {
"sum": {
"field": "price"
}
}
}
}最大值、最小值(max /min)
GET /product/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
},
"min_price": {
"min": {
"field": "price"
}
}
}
}例:
統(tǒng)計(jì)價(jià)格高于3000的手機(jī)品牌,形成一個(gè)個(gè)桶。對(duì)桶內(nèi)的商品做運(yùn)算,獲取每個(gè)品牌價(jià)格的最小值、最大值、平均值。
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手機(jī)"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": {
"stats_meric": {
"stats": {
"field": "price"
}
}
}
}
}
}stat聚合,可以同時(shí)獲取min、max、avg的結(jié)果。
.RestClient實(shí)現(xiàn)聚合
可以看到在DSL中,aggs聚合條件與query條件是同一級(jí)別,都屬于查詢JSON參數(shù)。因此依然是利用request.source()方法來(lái)設(shè)置。
不過(guò)聚合條件的要利用AggregationBuilders這個(gè)工具類來(lái)構(gòu)造。DSL與JavaAPI的語(yǔ)法對(duì)比如下:

聚合結(jié)果與搜索文檔同一級(jí)別,因此需要單獨(dú)獲取和解析。具體解析語(yǔ)法如下:

@Test
void testAgg() throws IOException {
// 1.創(chuàng)建Request
SearchRequest request = new SearchRequest("items");
// 2.準(zhǔn)備請(qǐng)求參數(shù)
BoolQueryBuilder bool = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("category", "手機(jī)"))
.filter(QueryBuilders.rangeQuery("price").gte(300000));
request.source().query(bool).size(0);
// 3.聚合參數(shù)
request.source().aggregation(
AggregationBuilders.terms("brand_agg").field("brand").size(5)
);
// 4.發(fā)送請(qǐng)求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 5.解析聚合結(jié)果
Aggregations aggregations = response.getAggregations();
// 5.1.獲取品牌聚合
Terms brandTerms = aggregations.get("brand_agg");
// 5.2.獲取聚合中的桶
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 5.3.遍歷桶內(nèi)數(shù)據(jù)
for (Terms.Bucket bucket : buckets) {
// 5.4.獲取桶內(nèi)key
String brand = bucket.getKeyAsString();
System.out.print("brand = " + brand);
long count = bucket.getDocCount();
System.out.println("; count = " + count);
}
}到此這篇關(guān)于Elasticsearch安裝 + CRUD + 查詢實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Elasticsearch 入門到實(shí)戰(zhàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis批量更新報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了Mybatis批量更新報(bào)錯(cuò)的問(wèn)題及解決辦法,包括mybatis批量更新的兩種方式,需要的的朋友參考下2017-01-01
利用靜態(tài)方法獲取spring的bean實(shí)例
文章介紹了如何在Spring框架中通過(guò)工具類的靜態(tài)方法獲取Spring命名空間中的bean,思路包括定義bean并實(shí)現(xiàn)ApplicationContextAware接口,然后在代碼中執(zhí)行特定代碼獲取bean,擴(kuò)展部分詳細(xì)講解了加載原理和注意事項(xiàng)2025-11-11
詳解spring boot實(shí)現(xiàn)多數(shù)據(jù)源代碼實(shí)戰(zhàn)
本篇文章主要介紹了詳解spring boot實(shí)現(xiàn)多數(shù)據(jù)源代碼實(shí)戰(zhàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07
SpringBoot部署和前端連接問(wèn)題解決的完整指南(net::ERR_CONNECTION_REFUSED)
在開(kāi)發(fā)和部署 Spring Boot 應(yīng)用時(shí),可能會(huì)遇到各種問(wèn)題,例如 JAR 文件無(wú)法運(yùn)行、前端無(wú)法連接后端服務(wù)等,本文將詳細(xì)總結(jié)這些問(wèn)題的解決方法,幫助你順利部署和運(yùn)行 Spring Boot 應(yīng)用,需要的朋友可以參考下2025-01-01
Spring事務(wù)管理只對(duì)出現(xiàn)運(yùn)行期異常進(jìn)行回滾
Spring的事務(wù)管理默認(rèn)只對(duì)出現(xiàn)運(yùn)行期異常(java.lang.RuntimeException及其子類)進(jìn)行回滾,需要了解更多Spring事務(wù)方面的知識(shí),可詳看本文2012-11-11
springboot實(shí)現(xiàn)配置本地訪問(wèn)端口及路徑
這篇文章主要介紹了springboot實(shí)現(xiàn)配置本地訪問(wèn)端口及路徑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java中Word與PDF轉(zhuǎn)換為圖片的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)將Word與PDF轉(zhuǎn)換為圖片,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-10-10

