ES中的一些重要概念
Elasticsearch是我们通常说的NoSQL(Not Only SQL)数据库,也就是非关系型数据库,作为ES有很多概念是和MySQL数据库重合的,我们就类比MySQL来介绍ES的重要概念
- index(索引):相当于mysql中的数据库;规范:这个名字必须小写,不能以下划线开头,不能包含逗号
- type(类型):相当于mysql中的一张表,7.x中已被移除
- document(文档):相当于mysql中的一行
- field(字段):相当于mysql中的一列
- mapping(映射):对应数据字段上的类型/主键/非空等约束(Schema)信息
Elasticsearch | MySQL |
---|---|
index(索引) | Database(数据库) |
Table(表) | |
document(文档) | Row(行) |
field(字段) | Column(列) |
mapping(映射) | Schema(约束) |
- node(节点):ES集群中的一台物理机或者虚拟机
- cluster(集群):一个或多个ES节点做相同的事
- shard(分片):将一份数据划分为多小份的能力,允许水平分割和扩展容量。多个分片可以响应请求,提高新能和吞吐量。把数据分摊到集群中的各个节点,就叫做分片
- replicas(副本):复制数据,一个节点出问题时,其余节点可以顶上
什么是RESTful?
Resful是一种软件架构风格,它的全称是Representational State Transfer,意思是表现层状态转换。它的核心思想是把网络上的数据和资源用统一的接口来操作,通过不同的HTTP方法(GET, POST, PUT, DELETE等)来实现对资源的增删改查。Resful API就是遵循Resful风格设计的网络接口,它通常用URL来表示资源,用HTTP方法来表示操作,用JSON或XML等格式来传输数据
URL | Method | 说明 |
---|---|---|
http://localhost/users | GET | 获取所有用户列表。GET方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据。 |
http://localhost/users/1 | GET | 获取id为1的用户信息。 |
http://localhost/users | POST | 创建一个新用户。POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
http://localhost/users/1 | PUT | 更新id为1的用户信息。PUT方法用有效载荷请求替换目标资源的所有当前表示。 |
http://localhost/users/1 | DELETE | 删除id为1的用户。DELETE方法删除指定的资源。 |
http://localhost/users/1 | HEAD | 获取id为1的用户的标头信息。HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体。这样可以节省带宽和服务器资源,也可以用来检查资源是否存在,或者获取资源的元数据。 |
如何在ES中实现索引的增删改查
ES的服务端采用RESTful风格进行API交互,在使用前我们最好安装Postman来简化操作,首先,启动Es的服务器程序,双击bin/elasticsearch.bat运行即可
- 新增job索引
PUT http://localhost:9200/job
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "job"
}
- 查询job索引元数据
GET http://localhost:9200/job
{
"job": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1686208851055",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "z8107hmJTLaWzKjWUwQpfA",
"version": {
"created": "7090399"
},
"provided_name": "job"
}
}
}
}
- 查询所有索引元数据
GET http://localhost:9200/_all
{
"job": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1686209375463",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "ZNwn9slWSnmQmNCa37QtMw",
"version": {
"created": "7090399"
},
"provided_name": "job"
}
}
}
}
- 查询索引的摘要信息
GET http://localhost:9200/_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open job ZNwn9slWSnmQmNCa37QtMw 1 1 0 0 208b 208b
字段 | 含义 |
---|---|
health | 健康状况 green:健康 yellow:存在风险 red:存在问题 |
status | open:打开 close:关闭 |
index | 索引名 |
uuid | 唯一标识 |
pri | 主分片数量 |
rep | 副本数量 |
doc.count | 文档(记录)总数 |
doc.deleted | 被删除的文档总量 |
store.size | 总占用空间 = 主分片+副本 |
pri.store.size | 主分片占用的数据总量 |
- 启用/关闭索引
POST http://localhost:9200/job/_close
POST http://localhost:9200/job/_open
- 删除索引
DELETE http://localhost:9200/job
{
"acknowledged": true
}
Mapping映射的作用
Mapping映射其实就是指数据的结构
如下文所示就是关于job(工作)索引的映射:
{
2 "properties": {
3 "jid" : {
4 "type" : "long"
5 },
6 "title": {
7 "type": "text" //text java软件工程师,java 软件 工程师
8 },
9 "salary" : {
10 "type" : "integer_range"
11 },
12 "city": {
13 "type": "keyword" #全字匹配 ,例如北京 天津
14 },
15 "description": { #模糊查询
16 "type": "text" #中华人民共和国 , 中华 人民 华人 共和国 ...
17 }
18 }
19 }
常见的数据类型
字符串
- text : 当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。text支持分词,模糊查询。
- keyword: keyword类型适用于索引结构化的字段,比如email地址、主机名、状态码和标签。如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合。keyword类型的字段只能通过精确值搜索到。keyword只允许进行全字匹配,不允许分词。
数值型
- 整型: byte,short,integer,long
- 浮点型: float, half_float, scaled_float,double
日期类型
以下是一个名为birthday的字段,他的类型是date,也就是日期类型。
并且指定了这个字段的一些属性,比如:
- format : 这个属性定义了日期字段的格式,可以是多种格式的组合,用双竖线分隔。在这里,指定了三种格式:
yyyy‐MM‐dd HH:mm:ss
、yyyy‐MM‐dd
和epoch_millis(毫秒值)
。这意味着可以用这三种格式来存储或查询日期数据。 - ignore_malformed : 这个属性决定了当遇到不符合格式的日期数据时,是否忽略它。如果设置为true,那么不符合格式的数据会被忽略,不会被索引或搜索。如果设置为false,那么不符合格式的数据会导致索引或搜索失败。在这里,你设置为false,表示要求日期数据必须符合格式。
- null_value : 这个属性定义了当遇到空值时,用什么值来替代它。如果设置为null,那么空值会被忽略,不会被索引或搜索。如果设置为其他值,比如
1970-01-01
,那么空值会被替换为这个值,并被索引或搜索。在这里,设置为null,表示不想处理空值。
{
"properties": {
"birthday": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis",
"ignore_malformed": false,
"null_value": null
}
}
}
范围型
integer_range, long_range, float_range,double_range,date_range
#创建integer_range类型索引字段
PUT integer_range_example
{
"mappings": {
"integerIndex": {
"properties": {
"num": {
"type": "integer_range"
}
}
}
}
}
#索引中加入数据
#gte(大于等于),lte(小于等于),也可以结合使用gt(大于),lt(小于)
#也可以以数组的形式put进去,进而达到多区间匹配
PUT integer_range_example/integerIndex/1?refresh
{
"num" : [
{"gte" : 10,"lte" : 20},
{"gt" : 30,"lt" : 40},
{"gte" : 50,"lt" : 60},
{ "gt" : 70,"lte" : 80}
]
}
#根据num检索结果
GET integer_range_example/_search
{
"query" : {
"term" : {
"num" : {
"value": 12 #10~20的区间命中
}
}
}
}
布尔 boolean
true,false
二进制 binary
会把值当作经过base64编码的字符串,默认不存储,且不可搜索
负载数据类型(不建议使用)
- object对象
- 数组
专用数据类型IP
{
//定义mapping
"ip_address": {
"type": "ip"
}
}
{
//插入|更新字段的值,值写成字符串形式
"ip": "192.168.1.1"
}
{
//搜索
"match": {
"ip_address": "192.168.1.1"
}
}
{
//ip在192.168.0.0 ~ 192.168.255.255上的文档都匹配
"match": {
"ip_address": "192.168.0.0/16"
}
}
新增Mapping映射
注意:ES中的映射Mapping只能新增字段,不能修改/删除映射下的任何字段或类型.这是硬性规定。ES的前期字段设计很重要,尤其是对名称和类型进行规划,避免出现重命名的情况
例:给job索引添加mapping映射,(其实相当于给job索引添加了一张表)
POST http://localhost:9200/job/_mapping
RequestBody(Json):
{
"properties": {
"jid": {
"type": "long"
},
"title": {
"type": "text"
},
"salary": {
"type": "integer_range"
},
"city": {
"type": "keyword"
},
"description": {
"type": "text"
}
}
}
查询mapping
POST http://localhost:9200/job/_mapping
如何实现已有映射字段的修改调整?
- 创建一个全新的索引(index),映射包含调整后的字段或类型 # job2
- 将原有索引下的数据迁移(reindex)到新的索引 #job reindex -> #job2
- 删除原有索引 #DELETE job
- 将新的索引的别名(alias)设置原有索引相同名称 #job2 alias -> job
Document的处理
索引结构:
{
"properties": {
"jid": {
"type": "long"
},
"title": {
"type": "text"
},
"company": {
"type": "text"
},
"salary": {
"type": "integer_range"
},
"city": {
"type": "keyword"
},
"description": {
"type": "text"
}
}
}
创建新文档
利用_create接口,可以新增Document,书写实例:
POST http://localhost:9200/job/_create/1
其中/1代表创建文档的id=1,对于重复的id,ES将报错
"reason": "[1]: version conflict, document already exists (current versio
n [3])",
请求体:
{
"jid": 1,
"title": "Java开发工程师",
"company": "北京威米信科技有限公司",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"description": "1、参与软件的架构设计、流程设计、数据库设计等工作;2、参与软件相关技术文档的编写;3、完成开发框架搭建;4、根据项目/产品需要,开展技术攻关工作,确定技术路线;5、配合项目经理完成项目建设任务。任职资格:1、本科以上学历,精通Java语言开发;2、熟练运用主流开源框架,如SSH,SpringMVC,MyBatis等;3、熟悉基于Oracle或者MySQL数据库的设计和开发,熟悉基于Ehcache,Redis,MongoDB的设计和开发;4、熟悉微服务spring boot、spring cloud、dubbo等,对业务中台、数据中台的设计思想深入理解;5、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息、负载均衡等机制和实现;6、具有Vue等前端技术经验者优先。7、对前后端分离,分库分表等技术有实际操作经验;8、熟悉docker、kubernetes等。9、熟悉敏捷开发和项目管理经验经验者;10、熟练使用git,svn。"
}
响应体
{
"_index": "job", //索引名称
"_type": "_doc", //文档类型
"_id": "1", //文档ID
"_version": 1, //文档版本
"result": "created", //created表示是第一次创建
"_shards": { //分片信息
"total": 2, //总分片数
"successful": 1, //成功分片数
"failed": 0 //失败分片数
},
"_seq_no": 0, //严格递增的顺序号,每个文档一个,Shard级别严格递增,保证后
写入的Doc的_seq_no大于先写入的Doc的_seq_no
"_primary_term": 1 //_primary_term也和_seq_no一样是一个整数,每当Primary
Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。
}
重建文档
重建文档是指将存在记录删除,再进行重建的操作,比如我们刚刚创建的文档为1号,现在我们进行重建,就会删除原来的1号文档,然后创建新的文档,文档ID同样为1
POST http://localhost:9200/job/_doc/1
{
"jid": 1,
"title": "Java开发工程师",
"company": "北京威米信科技有限公司",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"description": "1、参与软件的架构设计、流程设计、数据库设计等工作;2、参与软件相关技术文档的编写;3、完成开发框架搭建;4、根据项目/产品需要,开展技术攻关工作,确定技术路线;5、配合项目经理完成项目建设任务。任职资格:1、本科以上学历,精通Java语言开发;2、熟练运用主流开源框架,如SSH,SpringMVC,MyBatis等;3、熟悉基于Oracle或者MySQL数据库的设计和开发,熟悉基于Ehcache,Redis,MongoDB的设计和开发;4、熟悉微服务spring boot、spring cloud、dubbo等,对业务中台、数据中台的设计思想深入理解;5、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息、负载均衡等机制和实现;6、具有Vue等前端技术经验者优先。7、对前后端分离,分库分表等技术有实际操作经验;8、熟悉docker、kubernetes等。9、熟悉敏捷开发和项目管理经验经验者;10、熟练使用git,svn。"
}
响应体:
{
"_index": "job", //索引名称
"_type": "_doc", //文档类型
"_id": "1", //文档ID
"_version": 2, //文档版本,更新后增加了1
"result": "updated", //updated代表数据之前已存在,本次重建先删再创建
"_shards": { //分片信息
"total": 2, //总分片数
"successful": 1, //成功分片数
"failed": 0 //失败分片数
},
"_seq_no": 1, //序列号,更新后增加了1
"_primary_term": 1 //主分片期数,没有变化
}
获取文档
GET http://localhost:9200/job/_doc/1
响应体:
{
"_index": "job",
"_type": "_doc",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
"jid": 1,
"title": "Java开发工程师",
"company": "北京威米信科技有限公司",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"description": "1、参与软件的架构设计、流程设计、数据库设计等工作;2、参与软件相关技术文档的编写;3、完成开发框架搭建;4、根据项目/产品需要,开展技术攻关工作,确定技术路线;5、配合项目经理完成项目建设任务。任职资格:1、本科以上学历,精通Java语言开发;2、熟练运用主流开源框架,如SSH,SpringMVC,MyBatis等;3、熟悉基于Oracle或者MySQL数据库的设计和开发,熟悉基于Ehcache,Redis,MongoDB的设计和开发;4、熟悉微服务spring boot、spring cloud、dubbo等,对业务中台、数据中台的设计思想深入理解;5、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息、负载均衡等机制和实现;6、具有Vue等前端技术经验者优先。7、对前后端分离,分库分表等技术有实际操作经验;8、熟悉docker、kubernetes等。9、熟悉敏捷开发和项目管理经验经验者;10、熟练使用git,svn。"
}
}
获取多份文档
GET http://localhost:9200/job/_doc/_mget
响应体:
{
"docs": [
{
"_index": "job",
"_type": "_doc",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
"jid": 1,
"title": "Java开发工程师",
"company": "北京威米信科技有限公司",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"description": "1、参与软件的架构设计、流程设计、数据库设计等工作;2、参与软件相关技术文档的编写;3、完成开发框架搭建;4、根据项目/产品需要,开展技术攻关工作,确定技术路线;5、配合项目经理完成项目建设任务。任职资格:1、本科以上学历,精通Java语言开发;2、熟练运用主流开源框架,如SSH,SpringMVC,MyBatis等;3、熟悉基于Oracle或者MySQL数据库的设计和开发,熟悉基于Ehcache,Redis,MongoDB的设计和开发;4、熟悉微服务spring boot、spring cloud、dubbo等,对业务中台、数据中台的设计思想深入理解;5、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息、负载均衡等机制和实现;6、具有Vue等前端技术经验者优先。7、对前后端分离,分库分表等技术有实际操作经验;8、熟悉docker、kubernetes等。9、熟悉敏捷开发和项目管理经验经验者;10、熟练使用git,svn。"
}
},
{
"_index": "job",
"_type": "_doc",
"_id": "2",
"_version": 1,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"jid": 1,
"title": "Java开发工程师",
"company": "北京威米信科技有限公司",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"description": "1、参与软件的架构设计、流程设计、数据库设计等工作;2、参与软件相关技术文档的编写;3、完成开发框架搭建;4、根据项目/产品需要,开展技术攻关工作,确定技术路线;5、配合项目经理完成项目建设任务。任职资格:1、本科以上学历,精通Java语言开发;2、熟练运用主流开源框架,如SSH,SpringMVC,MyBatis等;3、熟悉基于Oracle或者MySQL数据库的设计和开发,熟悉基于Ehcache,Redis,MongoDB的设计和开发;4、熟悉微服务spring boot、spring cloud、dubbo等,对业务中台、数据中台的设计思想深入理解;5、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息、负载均衡等机制和实现;6、具有Vue等前端技术经验者优先。7、对前后端分离,分库分表等技术有实际操作经验;8、熟悉docker、kubernetes等。9、熟悉敏捷开发和项目管理经验经验者;10、熟练使用git,svn。"
}
}
]
}
更新文档(不建议使用)
更新文档字段
针对某个文档局部的字段值进行调整。处理不当可能导致es与数据库的数据不一致,建议使用文档重建的方式。
POST http://localhost:9200/job/_update/1
请求体-修改数据:将job索引中ID为1的文档的description字段值修改为"XXXX"
{
"script":"ctx._source.description = \"XXXX\""
}
删除文档字段
POST http://localhost:9200/job/_update/1
请求体-修改数据,将job索引中ID为1的文档中的description字段删除
{
"script":"ctx._source.remove(\"description\")"
}
删除文档
DELETE http://localhost:9200/job/_doc/1
Bulk API基本用法
ES中提供了批量导入,如接口Bulk,允许通过以下格式快速导入构建好的文档,接口格式如下:
Bulk接口:以下语法的意思是 给job索引,添加jid为15707和15708的索引。
注意:Bulk只支持小批量数据的导入,最大的允许单词请求100M,如果需要大规模批量导入数据,还需要借助logstash这样的三方工具
POST http://localhost:9200/_bulk
{
"index": {
"_index": "job",
"_type": "_doc",
"_id": "15707"
}
}
{
"jid": 15707,
"title": "Java开发工程师",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"company": "北京科技公司",
"description": "..."
}
{
"jid": 15708,
"title": "Java开发工程师",
"salary": {
"gte": 9000,
"lte": 15000
},
"city": "北京",
"company": "北京威米信科技有限公司",
"description": "..."
}
fresh参数说明:
三种使用形式:
# 默认,等待所有数据导入再刷新索引,即对查询暴露
POST http://localhost:9200/_bulk
# 每发生一条数据变化,立即刷新索引,对外暴露会影响性能
POST http://localhost:9200/_bulk?refresh
# 默认每一秒刷新一次索引,将最近一秒产生的数据刷新到索引
#可以通过设置index.refresh_interval修改刷新间隔
POST http://localhost:9200/_bulk?refresh=wait_for
全文检索的实现原理
Analyzer分词
什么是Analysis分词?
Analysis-文本分析是把全文本转换一系列单词(term / token)的过程,也叫分词
举例:默认英文句子是通过空格进行分词并进行小写转换
doc1:Nice to meet you 可以分词为 nice、to、meet、you
doc2:Nice day 可以分词为nice、day
什么是Analyzer分词器?
Analyzer分词器是分词的技术实现,在ES中就提供了以下多种分词器进行不同方式的分词操作
分词器 | 说明 |
---|---|
Standard Analyzer | 默认分词器,按词切分,小写处理 |
Simple Analyzer | 按照非字母切分(符号被过滤),小写处理 |
Stop Analyzer | 小写处理,停用词过滤(the, a, is) |
Whitespace Analyzer | 按照空格切分,不转小写 |
Keyword Analyzer | 不分词,直接将输入当作输出 |
Pattern Analyzer | 正则表达式,默认\W+(非字符分隔) |
Language | 提供了30多种常见语言的分词器 |
IK | 开源的中文分词器 |
THULAC | 聚类中文分词器 |
自定义 | 可以自定义字典,规则等来实现自定义分词 |
正/倒排索引
什么是正倒排索引?
正排索引(从文档到词)
doc1:Nice to meet you
文档编号 | 分词 |
---|---|
doc1 | nice |
doc1 | to |
doc1 | meet |
doc1 | you |
doc2:Nice day
文档编号 | 分词 |
---|---|
doc2 | nice |
doc2 | day |
倒排索引(从词到文档)
分词 | 文档编号:词频TF:出现位置POS:<偏移量OFFSET> |
---|---|
nice | doc1:1:0:<0,4>,doc2:1:0:<0,4> |
to | doc1:1:1:<5,7> |
meet | doc1:1:2:<8,14> |
you | doc1:1:3:<15,18> |
day | doc2:1:1:<5,8> |
假设用户查询“nice”单词时,便可以快速查询获得doc1与doc2两个文档。
Elasticsearch的JSON文档中的每个字段,都有自己的倒排索引。
基于相关性算分的文档排序
对于前面案例,搜索“nice”时快速获得doc1与doc2两个文档,但这两个文档哪个在前,哪个在后呢?这是由相关性算分得到的。
搜索的相关性算分,描述了一个文档和查询语句匹配的程度。ES会对每个匹配查询条件的结果进行算分_score,分值越高的说明文档越符合预期,排名也就越靠前。
TF-IDF算法
TF-IDF中几个关键概念:
Term Frequency(TF):词频(TF)表示一个词在一个文档中出现的次数,它反映了一个词在文档中的重要程度。一般来说,一个词出现的次数越多,它就越重要。但是,并不是所有的词都有同样的价值,有些词可能很常见,但并不代表文档的主题,比如“的”、“是”、“有”等。所以,我们需要对词频进行一定的调整,使得它能更好地反映一个词的真实重要程度。
IDF:逆文档频率(IDF)表示一个词在所有文档中出现的频率的倒数,它反映了一个词的稀有程度。一般来说,一个词在所有文档中出现的次数越少,它就越稀有,也就越能区分不同的文档。比如,“Elasticsearch”这个词可能只在少数文档中出现,而“搜索”这个词可能在很多文档中出现。那么,“Elasticsearch”就比“搜索”更能体现文档的特征,也就应该有更高的权重。IDF=log(全部文档数/检索词出现过的文档总数)
TF-IDF 的计算公式为:TF-IDF = TF * IDF。通过计算每个查询词的 TF-IDF 值,可以确定查询词与文档之间的相关性得分,从而对搜索结果进行排序。TF-IDF值越高,说明一个词对一个文档的贡献越大,也就是相关性越高。
假设我们有三个文档,它们的内容如下:
- 文档1:Elasticsearch 是一个分布式的搜索和分析引擎。
- 文档2:Elasticsearch 可以处理各种类型的数据,如文本、数字、地理位置等。
- 文档3:Elasticsearch 是基于Lucene 开发的,它提供了一个简单的RESTful API。
假如我们有三个词语(同时搜索三个词语),它们是:
- 词语1:Elasticsearch
- 词语2:数据
- 词语3:Lucene
我们用ik_smart分词器对这些文档进行分词,得到以下结果:
- 文档1:Elasticsearch / 是 / 一个 / 分布式 / 的 / 搜索 / 和 / 分析 / 引擎
- 文档2:Elasticsearch / 可以 / 处理 / 各种 / 类型 / 的 / 数据 / , / 如 / 文本 / 、 / 数字 / 、 / 地理位置 / 等
- 文档3:Elasticsearch / 是 / 基于 / Lucene / 开发 / 的 / , / 它 / 提供了 / 一个 / 简单 / 的 / RESTful API
我们用原始词频的方式来计算TF值,用log的方式来计算IDF值,得到以下结果:
词语 | 文档 | TF值 | IDF值 |
---|---|---|---|
Elasticsearch | 文档1 | 1 | 0 |
Elasticsearch | 文档2 | 1 | 0 |
Elasticsearch | 文档3 | 1 | 0 |
数据 | 文档1 | 0 | 0.48 |
数据 | 文档2 | 1 | 0.48 |
数据 | 文档3 | 0 | 0.48 |
Lucene | 文档1 | 0 | 0.48 |
Lucene | 文档2 | 0 | 0.48 |
Lucene | 文档3 | 1 | 0.48 |
然后,我们将TF和IDF相乘得到TF-IDF值,得到以下结果:
词语 | 文档 | TF-IDF值 |
---|---|---|
Elasticsearch | 文档1 | 0 |
Elasticsearch | 文档2 | 0 |
Elasticsearch | 文档3 | 0 |
数据 | 文档1 | 0 |
数据 | 文档2 | 0.48 |
数据 | 文档3 | 0 |
Lucene | 文档1 | 0 |
Lucene | 文档2 | 0 |
Lucene | 文档3 | 0.48 |
最后,我们将每个文档中的所有词语的TF-IDF值相加得到文档的相关性得分,得到以下结果:
文档 | 相关性得分 |
---|---|
文档1 | 0 |
文档2 | 0.48 |
文档3 | 0.48 |
BM25算法
在ES5以后,默认采用BM25算法,和经典的 TF-IDF相比,当TF无限增加时,BM 25算法会趋于一个数值
bm25的公式
URL Query 快速检索
Elasticsearch 通过 URL Query方式可以实现快速实现数据检索
标准格式:
GET http://localhost:9200/job/_search?q=springboot&df=description&sort=jid:desc&from=0&size=10&timeout=1s
参数列表:
- q:查询的内容,使用Query String syntax表达
- df:指定默认字段,不指定时对所有字段进行查询
- sort:排序字段,格式:字段名:desc/asc
- from/size:分页的起始行号与每页记录数
单字段查询
只需要在q参数中设置 字段名:值,便可按指定字段实现查询
GET http://localhost:9200/job/_search?q=description:spring
description字段中只要包含spring就会被选中
Phrase查询
Phrase查询会按照查询保证按分词的前后位置进行查询
需要在值前后增加引号
GET http://localhost:9200/job/_search?q=description:"spring cloud"
结果中description字段包含spring...cloud会被选中,如包含cloud spring则不会被选中
Boolean操作查询
查询description字段同时包含spring与mysql的文档,对位置不做限定
GET http://localhost:9200/job/_search?q=description:(spring AND mysql)
查询description字段包含spring或mysql的文档
GET http://localhost:9200/job/_search?q=description:(spring OR mysql)
查询description字段包含spring但不允许出现mysql的文档
GET http://localhost:9200/job/_search?q=description:( spring NOT mysql)
范围查询
可以提供20000以上月薪的工作
GET http://localhost:9200/job/_search?q=salary:>=20000
通配符查询
查询title字段中以java开头的词
GET http://localhost:9200/job/_search?q=title:java*
模糊匹配&近似度匹配
查询description中包含与springbot 相似单词的结果
ET http://localhost:9200/job/_search?q=description:springbot~1
查询description中允许在两个单词间隔内,同时出现jstl与jquery文档,且允许不按顺序排列
GET http://localhost:9200/job/_search?q=description:"jstl jquery"~2
Request Body Search
将查询语句通过HTTP Requedt Body发送给Elasticsearch,相比URI Query,Request Body Search配合QueryDSL提供了功能更为强大的查询语法,在开发时更推荐使用Request Body Search(RBS)。
GET http://localhost:9200/job/_search
请求体包含QueryDSL结构
{
"query": {
"match_all": {}
}
}
tips:这里有一个小问题,RBS支持GET与POST,但GET W3C标准是没有请求体的,而POST则在RESTful风格中代表写入而非查询,尽管ES两者都支持,我还是更推荐使用GET方法,因为它符合语义。
QueryDSL的常见用法
Query DSL很多选项与Query URI是重合的。
{
"from": 0, // 分页
"size": 10, // 每页记录数
"sort": [ // 排序
{
"jid": "desc"
}
],
"_source": [ // 返回 jid、title和salary三个字段
"jid",
"title",
"salary"
],
"query": { // 具体查询的要求
"match_all": {} // 匹配所有文档
}
}
Query查询常见类型
精准匹配
单值精准匹配,使用term
GET http://localhost:9200/job/_search
请求体:
{
"query": {
"term": {
"jid": 15707
}
}
}
多值精准匹配,使用terms
{
"query": {
"terms": {
"jid": [15707, 15708, 15709]
}
}
}
单字段全文检索
查询岗位描述中包含“金融”的数据
{
"query": {
"match": {
"description": {
"query": "金融"
}
}
}
}
简化写法
{
"query": {
"match": {
"description": "金融"
}
}
}
查询所有文档
{
"query": {
"match_all": {}
}
}
不查询任何文档
{
"query": {
"match_none": {}
}
}
多字段查询
查询company与description包含“金融”的数据
{
"query": {
"multi_match": {
"query": "金融",
"fields": ["company", "description"]
}
}
}
单字段Boolean查询
要求title同时出现“java”与“架构师”两个词才满足条件
{
"query": {
"match": {
"title": {
"query": "java 架构师",
"operator": "and"
}
}
}
}
Phrase查询
{
"query": {
"match_phrase": {
"description": { // 匹配字段为 description
"query": "was tomcat", // 查询短语为 "was tomcat"
"slop": 1 // 允许 "was" 和 "tomcat" 之间最多只能有一个额外的词
}
}
}
}
范围查询
只要和文档数据产生交集便会被选中,返回所有 salary 字段大于等于 5000,且小于等于 20000 的文档
{
"query": {
"range": {
"salary": {
"gte": "5000",
"lte": "20000"
}
}
}
}
正则表达式查询
通常使用在keyword类型上
{
"query": {
"regexp": {
"city": {
"value": "[北|南]京"
}
}
}
}
bool多字段复合查询
- Bool 查询适用于将多个查询子句组合在一起进行搜索。
- Bool 查询包括 4 种子句,其中 2 种会影响搜索结果的相关性评分,另外 2 种不会影响评分。
- 相关性评分的计算不仅适用于全文本检索,也适用于 Yes/No 查询子句。如果一个文档匹配的查询子句越多,那么该文档的相关性评分就越高。
- 如果多条查询子句被合并为一条复合查询语句,比如 Bool 查询,那么每个查询子句计算得出的评分会被合并到总的相关性评分中。
子句类型 | 说明 |
---|---|
must | 必须匹配,不符合must的文档一定不会出现在结果中,参与算分 |
filter | 必须匹配,不符合filter的文档一定不会出现在结果中,但不参与算分filter在查询前先执行过滤,不涉及算分,因此filter结果可以被ES缓存,使用filter效率比must要高。 |
must_not | 排除在外,符合must_not的文档一定不会出现在结果中 |
should | 可有可无的选择,参与算分。如没有must,则至少有一个should子句符合条件才可以出现在结果中 |
Bootsing查询
Bootstring查询允许根据内容调整Boosting最终算分
用户输入了关键字 "vue",但是我们想要排除掉那些同时包含 "jquery" 的商品。这时候,我们可以使用 Boosting Query,将 "description": "vue" 作为正样本查询子句,将 "description": "jquery" 作为负样本查询子句,然后将负样本查询子句的权重降低一些,以此来得到更好的搜索结果。
{
"query": {
"boosting": {
"positive": { //表示正样本查询子句
"match": {
"description": "vue"
}
},
"negative": { //表示负样本查询子句
"match": {
"description": "jquery"
}
},
"negative_boost": 0.8 //表示将负样本查询子句的影响降低 20%
}
}
}
练习题:
某猎头公司为客户匹配工作岗位,客户要求工作地在北京或重庆,工资不得低于6000的java工程师岗位,项目要求使用spring cloud技术,如涉及shell编程的则重点考虑。
{
"query": {
"bool": {
"must": {
"match": {
"title": {
"query": "java 工程师",
"operator": "and"
}
}
},
"filter": {
"terms": {"city": ["北京", "重庆"]}
},
"must_not": {
"range": {
"salary": {"lt": "6000"}
}
},
"should": [
{
"match_phrase": {
"description": {
"query": "spring cloud",
"boost": 2 // 匹配到的文档的得分乘以 2
}
}
},
{
"match": {
"description": {
"query": "shell"
"boost": 5 // 匹配到的文档的得分乘以 5
}
}
}
],
"minimum_should_match": 1 //表示至少需要匹配一个 should 子句才能将文档作为搜索结果返回
}
}
}
中文分词与IK分词器
ES中常用的中文分词器
IK 分词器 - 使用最多的分词器:
HanLP - 面向生产环境的自然语言处理包:
安装IK分词器
1.下载与你ES对应版本的IK分词器,比如我现在es版本为7.16.2,IK分词器也下载7.16.2
https://github.com/medcl/elasticsearch-analysis-ik/releases
下载后到elasticsearch根目录下进入plugins,然后创建ik文件夹,将压缩包的文件放入ik文件夹
然后重启elasticsearch
IK分词器配置
IK分词器有两种分词模式:ik_max_word 和 ik_smart 模式
1、ik_max_word
会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
2、ik_smart
会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
分析IK结果
查看分词结果
GET http://localhost:9200/_analyze
请求体
{
"analyzer": "ik_max_word",
"text": [
"健康的饮食和锻炼对身体健康非常重要"
]
}
响应体
{
"tokens": [
{
"token": "健康",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "的",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 1
},
{
"token": "饮食",
"start_offset": 3,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
},
{
"token": "和",
"start_offset": 5,
"end_offset": 6,
"type": "CN_CHAR",
"position": 3
},
{
"token": "锻炼",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 4
},
{
"token": "对",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 5
},
{
"token": "身体健康",
"start_offset": 9,
"end_offset": 13,
"type": "CN_WORD",
"position": 6
},
{
"token": "身体",
"start_offset": 9,
"end_offset": 11,
"type": "CN_WORD",
"position": 7
},
{
"token": "健康",
"start_offset": 11,
"end_offset": 13,
"type": "CN_WORD",
"position": 8
},
{
"token": "非常重要",
"start_offset": 13,
"end_offset": 17,
"type": "CN_WORD",
"position": 9
},
{
"token": "非常",
"start_offset": 13,
"end_offset": 15,
"type": "CN_WORD",
"position": 10
},
{
"token": "重要",
"start_offset": 15,
"end_offset": 17,
"type": "CN_WORD",
"position": 11
}
]
}
IK自定义词库
找到es-root\plugins\ik\config目录,新建custom目录,在目录下创建字典文件
mydict.txt,其中每一行书写一个新词即可,例如我们让这一句话为一个词:
健康的饮食和锻炼对身体健康非常重要
打开es-root\plugins\ik\config\IKAnalyzer.cfg.xml
增加自定义词库配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom/mydict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
重启ES,重新分析,查看结果
{
"tokens": [
{
"token": "健康的饮食和锻炼对身体健康非常重要",
"start_offset": 0,
"end_offset": 17,
"type": "CN_WORD",
"position": 0
},
{
"token": "健康",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "的",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
},
{
"token": "饮食",
"start_offset": 3,
"end_offset": 5,
"type": "CN_WORD",
"position": 3
},
{
"token": "和",
"start_offset": 5,
"end_offset": 6,
"type": "CN_CHAR",
"position": 4
},
{
"token": "锻炼",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 5
},
{
"token": "对",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 6
},
{
"token": "身体健康",
"start_offset": 9,
"end_offset": 13,
"type": "CN_WORD",
"position": 7
},
{
"token": "身体",
"start_offset": 9,
"end_offset": 11,
"type": "CN_WORD",
"position": 8
},
{
"token": "健康",
"start_offset": 11,
"end_offset": 13,
"type": "CN_WORD",
"position": 9
},
{
"token": "非常重要",
"start_offset": 13,
"end_offset": 17,
"type": "CN_WORD",
"position": 10
},
{
"token": "非常",
"start_offset": 13,
"end_offset": 15,
"type": "CN_WORD",
"position": 11
},
{
"token": "重要",
"start_offset": 15,
"end_offset": 17,
"type": "CN_WORD",
"position": 12
}
]
}
索引中应用IK分词
应用分词器需要重建mapping,我们新建一个索引名"joke"
PUT http://localhost:9200/joke
在构建mapping时,为指定text类型字段分配IK分词器
POST http://localhost:9200/joke/_mapping
请求体
{
"properties": {
"content": {
"type": "text",
"analyzer":"ik_max_word"
}
}
}
创建测试数据
POST http://localhost:9200/joke/_create/1
请求体
{
"content": "早上起床后,记得要喝一杯温水。今天天气真好,适合出门旅游"
}
查询
GET http://localhost:9200/joke/_search
响应体
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.1507283,
"hits": [
{
"_index": "joke",
"_type": "_doc",
"_id": "1",
"_score": 1.1507283,
"_source": {
"content": "早上起床后,记得要喝一杯温水。今天天气真好,适合出门旅游"
}
}
]
}
}
利用Completion Suggester实现智能提示
Completion Suggester提供了 自动完成" (Auto Complete)的功能,类似于使用浏览器的时候,搜索提示功能,用户每输入一个字符,就需要即时发送一个查询请求到后段查找匹配项
案例演示:
- 重新建立job索引
DELETE http://localhost:9200/job
PUT http://localhost:9200/job
- 添加mapping映射
{
"properties": {
"jid": {
"type": "long" // 长整型数字,表示某个职位的编号
},
"title": {
"type": "text" // 文本类型,表示某个职位的标题
},
"title_completion": {
"type": "completion" // 补全类型,表示某个职位的标题可以用来做自动补全功能
},
"salary_text": {
"type": "text", // 文本类型,表示某个职位的薪资
"index": false // 不用于索引
},
"city": {
"type": "keyword" // 关键字类型,表示某个职位所在的城市,关键字不分词
}
}
}
- 添加素材
http://localhost:9200/_bulk
请求体:
训练素材:https://cdn.oolo.cc/source/bulk-job1.txt
{ "index": { "_index": "job", "_type": "_doc", "_id": "15707" }}
{"jid":15707,"title":"Java开发工程师","city":"北京","company":"北京威米信科技有限公司","title_completion":"Java开发工程师","salary_text":"0.9-1.5万/月"}
{ "index": { "_index": "job", "_type": "_doc", "_id": "15708" }}
{"jid":15708,"title":"JAVA开发工程师","city":"北京","company":"北京市商汤科技开发有限公司","title_completion":"JAVA开发工程师","salary_text":"1.5-3万/月"}
.......
补全查询:
GET http://localhost:9200/job/_search
请求体:
{
"suggest" :{ // 表示要进行自动补全的请求
"title-suggest":{ // 表示要对职位标题进行自动补全(可随便写,这里是什么,返回的就是什么)
"prefix":"java工程师", // 表示要补全的前缀
"completion":{ // 表示要使用的补全策略
"field":"title_completion", // 表示要使用的字段
"analyzer":"ik_max_word", // 表示要使用的分词器
"size":5, // 表示要返回的结果数量
"skip_duplicates": true // 表示是否跳过重复的结果
}
}
}
}
响应体:
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"suggest": {
"title-suggest": [
{
"text": "java工程师",
"offset": 0,
"length": 7,
"options": [
{
"text": "JAVA工程师",
"_index": "job",
"_type": "_doc",
"_id": "15759",
"_score": 1.0,
"_source": {
"jid": 15759,
"title": "JAVA工程师",
"city": "北京",
"company": "北京千喜鹤餐饮管理有限公司",
"title_completion": "JAVA工程师",
"salary_text": "1.5-2万/月"
}
},
{
"text": "Java工程师",
"_index": "job",
"_type": "_doc",
"_id": "15790",
"_score": 1.0,
"_source": {
"jid": 15790,
"title": "Java工程师",
"city": "北京",
"company": "北京商连信息科技有限公司",
"title_completion": "Java工程师",
"salary_text": "6-8千/月"
}
},
{
"text": "Java工程师(8JW51)",
"_index": "job",
"_type": "_doc",
"_id": "15947",
"_score": 1.0,
"_source": {
"jid": 15947,
"title": "Java工程师(8JW51)",
"city": "北京",
"company": "上海博泰悦臻电子设备制造有限公司",
"title_completion": "Java工程师(8JW51)"
}
},
{
"text": "Java工程师(北京)",
"_index": "job",
"_type": "_doc",
"_id": "15744",
"_score": 1.0,
"_source": {
"jid": 15744,
"title": "Java工程师(北京)",
"city": "北京",
"company": "广东道一信息技术股份有限公司",
"title_completion": "Java工程师(北京)",
"salary_text": "0.8-1万/月"
}
},
{
"text": "java工程师",
"_index": "job",
"_type": "_doc",
"_id": "15784",
"_score": 1.0,
"_source": {
"jid": 15784,
"title": "java工程师",
"city": "北京",
"company": "北京埃可斯科技有限责任公司",
"title_completion": "java工程师",
"salary_text": "1.2-2万/月"
}
}
]
}
]
}
}
Bucket_Metrix聚合分析
Bucket Metrix 是一种聚合分析类型,它可以将文档根据一定的条件分成不同的桶(Bucket),然后对每个桶内的文档进行指标(Metric)分析,比如计算平均值、最大值、最小值等。它相当于 SQL 中的 GROUP BY 和 COUNT 的组合。例如,可以用 Bucket Metrix 来统计不同目的地的航班的机票均价、最高最低价格等。
- Bucket - 分组的数据,例如RESEARCH、SALES、ACCOUNTING
- Metric - 针对分组数据的汇总计算,如count(*)、max(salary)
创建employee索引
PUT http://localhost:9200/employee
创建mapping映射
PUT http://localhost:9200/employee/_mapping
{
"properties": {
"empno": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"job": {
"type": "keyword"
},
"salary": {
"type": "integer"
},
"age": {
"type": "integer"
},
"gender":{
"type":"keyword"
},
"dname":{
"type":"keyword",
"eager_global_ordinals":true
}
}
}
使用_bulk添加文档
PUT http://localhost:9200/_bulk
{"index": {"_index": "employee", "_type": "_doc"}}
{"empno": 7782, "name": "CLARK", "employee": "MANAGER", "salary": 2450.00, "age": 41, "gender":"MALE", "dname":"ACCOUNTING"}
{"index": {"_index": "employee", "_type": "_doc"}}
{"empno": 7839, "name": "KING", "employee": "PRESIDENT", "salary": 5000.00, "age": 52, "gender":"MALE", "dname":"ACCOUNTING"}
{"index": {"_index": "employee", "_type": "_doc"}}
{"empno": 7934, "name": "MILLER", "employee": "CLERK", "salary": 1300.00, "age": 23, "gender":"MALE", "dname":"ACCOUNTING"}
{"index": {"_index": "employee", "_type": "_doc"}}
{"empno": 7566, "name": "JONES", "employee": "MANAGER", "salary": 2975.00, "age": 38, "gender":"MALE", "dname":"RESEARCH"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7902,"name" :"FORD","employee" :"ANALYST","salary" :3000.00,"age" :38,"gender":"MALE","dname" :"RESEARCH"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7369,"name" :"SMITH","employee" :"CLERK","salary" :800.00,"age" :22,"gender":"MALE","dname" :"RESEARCH"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7499,"name" :"ALLEN","employee" :"SALESMAN","salary" :1600.00,"age" :26,"gender":"MALE","dname" :"SALES"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7521,"name" :"WARD","employee" :"SALESMAN","salary" :1250.00,"age" :22,"gender":"MALE","dname" :"SALES"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7654,"name" :"MARTIN","employee" :"SALESMAN","salary" :1250.00,"age" :28,"gender":"MALE","dname" :"SALES"}
{"index":{"_index":"employee","_type":"_doc"}}
{"empno" :7698,"name" :"BLAKE","employee" :"MANAGER","salary" :2850.00,"age" :35,"gender":"MALE","dname" :"SALES"}
Bucket统计
单字段统计
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回的结果数量,这里设置为0,表示不返回任何文档
"aggs": {
"min_salary": { // 对salary字段进行最小值的聚合操作
"min": {
"field": "salary"
}
},
"max_salary": { // 对salary字段进行最大值的聚合操作
"max": {
"field": "salary"
}
},
"avg_salary": { // 对salary字段进行平均值的聚合操作
"avg": {
"field": "salary"
}
}
}
}
通常与聚合相关的查询size=0,代表只返回聚合相关的结果,不查询任何明细数据
响应
{
"took": 171,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"max_salary": {
"value": 5000.0
},
"avg_salary": {
"value": 2247.5
},
"min_salary": {
"value": 800.0
}
}
}
单字段批量统计
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回结果的数量,0 表示不返回任何文档,只返回聚合结果
"aggs": { // 聚合查询语句的主体部分,包含一个或多个聚合子句
"stats_salary": { // 聚合子句的名称,用于标识聚合结果
"stats": { // 统计聚合子句,用于计算指定字段的统计信息(count,min,max,avg,sum)
"field": "salary" // 指定进行统计的字段名
}
}
}
}
响应体
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"stats_salary": {
"count": 10,
"min": 800.0,
"max": 5000.0,
"avg": 2247.5,
"sum": 22475.0
}
}
}
Terms统计每个部门的人数
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回结果的数量,0 表示不返回任何文档,只返回聚合结果
"aggs": { // 聚合查询语句的主体部分,包含一个或多个聚合子句
"department": { // 聚合子句的名称,用于标识聚合结果
"terms": { // 分组聚合子句,用于按指定字段进行分组
"field": "dname" // 指定按照 dname 字段进行分组
}
}
}
}
响应体:
{
"took": 17,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"department": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "SALES",
"doc_count": 4
},
{
"key": "ACCOUNTING",
"doc_count": 3
},
{
"key": "RESEARCH",
"doc_count": 3
}
]
}
}
}
Terms统计每个部门的工资最高的2人
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回结果的数量,0 表示不返回任何文档,只返回聚合结果
"aggs": { // 聚合查询语句的主体部分,包含一个或多个聚合子句
"department": { // 聚合子句的名称,用于标识聚合结果
"terms": { // 分组聚合子句,用于按指定字段进行分组
"field": "dname" // 指定按照 dname 字段进行分组
},
"aggs": { // 嵌套聚合子句,用于在每个分组中进行聚合操作
"top_salary": { // 聚合子句的名称,用于标识聚合结果
"top_hits": { // 获取每个分组中排名前两位的文档
"size": 2, // 指定每个分组中返回的文档数量为2
"sort": [ // 指定排序规则
{
"salary": { // 指定按照 salary 字段进行排序
"order": "desc" // 指定按照降序排序
}
}
]
}
}
}
}
}
}
响应体:
{
"took": 18,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"department": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "SALES",
"doc_count": 4,
"top_salary": {
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "S6gCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7698,
"name": "BLAKE",
"employee": "MANAGER",
"salary": 2850.00,
"age": 35,
"gender": "MALE",
"dname": "SALES"
},
"sort": [
2850
]
},
{
"_index": "employee",
"_type": "_doc",
"_id": "SKgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7499,
"name": "ALLEN",
"employee": "SALESMAN",
"salary": 1600.00,
"age": 26,
"gender": "MALE",
"dname": "SALES"
},
"sort": [
1600
]
}
]
}
}
},
{
"key": "ACCOUNTING",
"doc_count": 3,
"top_salary": {
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "Q6gCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7839,
"name": "KING",
"employee": "PRESIDENT",
"salary": 5000.00,
"age": 52,
"gender": "MALE",
"dname": "ACCOUNTING"
},
"sort": [
5000
]
},
{
"_index": "employee",
"_type": "_doc",
"_id": "QqgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7782,
"name": "CLARK",
"employee": "MANAGER",
"salary": 2450.00,
"age": 41,
"gender": "MALE",
"dname": "ACCOUNTING"
},
"sort": [
2450
]
}
]
}
}
},
{
"key": "RESEARCH",
"doc_count": 3,
"top_salary": {
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "RqgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7902,
"name": "FORD",
"employee": "ANALYST",
"salary": 3000.00,
"age": 38,
"gender": "MALE",
"dname": "RESEARCH"
},
"sort": [
3000
]
},
{
"_index": "employee",
"_type": "_doc",
"_id": "RagCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7566,
"name": "JONES",
"employee": "MANAGER",
"salary": 2975.00,
"age": 38,
"gender": "MALE",
"dname": "RESEARCH"
},
"sort": [
2975
]
}
]
}
}
}
]
}
}
}
RANGE对数据范围Bucket分桶
统计各年龄段最高工资
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回结果的数量,0 表示不返回任何文档,只返回聚合结果
"aggs": { // 聚合查询语句的主体部分,包含一个或多个聚合子句
"age_range": { // 聚合子句的名称,用于标识聚合结果
"range": { // 范围聚合子句,用于按指定范围进行分组
"field": "age", // 指定按照 age 字段进行范围分组
"ranges": [ // 指定范围的数组
{"key": "20岁以下", "to": 20},
{"key": "20-30岁", "from": 20, "to": 30},
{"key": "30-40岁", "from": 30, "to": 40},
{"key": "40-50岁", "from": 40, "to": 50},
{"key": "50岁以上", "from": 50}
]
},
"aggs": { // 嵌套聚合子句,用于在每个范围分组中进行聚合操作
"top_salary": { // 聚合子句的名称,用于标识聚合结果
"top_hits": { // 获取每个范围分组中排名前一位的文档
"size": 1, // 指定每个分组中返回的文档数量为1
"sort": [ // 指定排序规则
{
"salary": { // 指定按照 salary 字段进行排序
"order": "desc" // 指定按照降序排序
}
}
]
}
}
}
}
}
}
响应体:
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"age_range": {
"buckets": [
{
"key": "20岁以下",
"to": 20.0,
"doc_count": 0,
"top_salary": {
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
},
{
"key": "20-30岁",
"from": 20.0,
"to": 30.0,
"doc_count": 5,
"top_salary": {
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "SKgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7499,
"name": "ALLEN",
"employee": "SALESMAN",
"salary": 1600.00,
"age": 26,
"gender": "MALE",
"dname": "SALES"
},
"sort": [
1600
]
}
]
}
}
},
{
"key": "30-40岁",
"from": 30.0,
"to": 40.0,
"doc_count": 3,
"top_salary": {
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "RqgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7902,
"name": "FORD",
"employee": "ANALYST",
"salary": 3000.00,
"age": 38,
"gender": "MALE",
"dname": "RESEARCH"
},
"sort": [
3000
]
}
]
}
}
},
{
"key": "40-50岁",
"from": 40.0,
"to": 50.0,
"doc_count": 1,
"top_salary": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "QqgCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7782,
"name": "CLARK",
"employee": "MANAGER",
"salary": 2450.00,
"age": 41,
"gender": "MALE",
"dname": "ACCOUNTING"
},
"sort": [
2450
]
}
]
}
}
},
{
"key": "50岁以上",
"from": 50.0,
"doc_count": 1,
"top_salary": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "employee",
"_type": "_doc",
"_id": "Q6gCpogBvGUipZomwYPh",
"_score": null,
"_source": {
"empno": 7839,
"name": "KING",
"employee": "PRESIDENT",
"salary": 5000.00,
"age": 52,
"gender": "MALE",
"dname": "ACCOUNTING"
},
"sort": [
5000
]
}
]
}
}
}
]
}
}
}
Histogram阶梯分桶
按5岁一个阶梯统计各阶段工资汇总数据
GET http://localhost:9200/employee/_search
{
"size": 0, // 返回结果的数量,0 表示不返回任何文档,只返回聚合结果
"aggs": { // 聚合查询语句的主体部分,包含一个或多个聚合子句
"age_histogram": { // 聚合子句的名称,用于标识聚合结果
"histogram": { // 直方图聚合子句,用于按指定间隔进行分组
"field": "age", // 指定按照 age 字段进行分组
"interval": 5, // 指定间隔为5岁
"extended_bounds": { // 指定分组范围的上下限
"min": 18,
"max": 65
}
},
"aggs": { // 嵌套聚合子句,用于在每个分组中进行聚合操作
"stats_salary": { // 聚合子句的名称,用于标识聚合结果
"stats": { // 统计聚合子句,用于计算指定字段的统计信息
"field": "salary" // 指定计算 salary 字段的统计信息
}
}
}
}
}
}
响应体:
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"age_histogram": {
"buckets": [
{
"key": 15.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
},
{
"key": 20.0,
"doc_count": 3,
"stats_salary": {
"count": 3,
"min": 800.0,
"max": 1300.0,
"avg": 1116.6666666666667,
"sum": 3350.0
}
},
{
"key": 25.0,
"doc_count": 2,
"stats_salary": {
"count": 2,
"min": 1250.0,
"max": 1600.0,
"avg": 1425.0,
"sum": 2850.0
}
},
{
"key": 30.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
},
{
"key": 35.0,
"doc_count": 3,
"stats_salary": {
"count": 3,
"min": 2850.0,
"max": 3000.0,
"avg": 2941.6666666666665,
"sum": 8825.0
}
},
{
"key": 40.0,
"doc_count": 1,
"stats_salary": {
"count": 1,
"min": 2450.0,
"max": 2450.0,
"avg": 2450.0,
"sum": 2450.0
}
},
{
"key": 45.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
},
{
"key": 50.0,
"doc_count": 1,
"stats_salary": {
"count": 1,
"min": 5000.0,
"max": 5000.0,
"avg": 5000.0,
"sum": 5000.0
}
},
{
"key": 55.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
},
{
"key": 60.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
},
{
"key": 65.0,
"doc_count": 0,
"stats_salary": {
"count": 0,
"min": null,
"max": null,
"avg": null,
"sum": 0.0
}
}
]
}
}
}