ES中的一些重要概念

Elasticsearch是我们通常说的NoSQL(Not Only SQL)数据库,也就是非关系型数据库,作为ES有很多概念是和MySQL数据库重合的,我们就类比MySQL来介绍ES的重要概念

  1. index(索引):相当于mysql中的数据库;规范:这个名字必须小写,不能以下划线开头,不能包含逗号
  2. type(类型):相当于mysql中的一张表,7.x中已被移除
  3. document(文档):相当于mysql中的一行
  4. field(字段):相当于mysql中的一列
  5. mapping(映射):对应数据字段上的类型/主键/非空等约束(Schema)信息
ElasticsearchMySQL
index(索引)Database(数据库)
type(类型)Table(表)
document(文档)Row(行)
field(字段)Column(列)
mapping(映射)Schema(约束)
  1. node(节点):ES集群中的一台物理机或者虚拟机
  2. cluster(集群):一个或多个ES节点做相同的事
  3. shard(分片):将一份数据划分为多小份的能力,允许水平分割和扩展容量。多个分片可以响应请求,提高新能和吞吐量。把数据分摊到集群中的各个节点,就叫做分片
  4. replicas(副本):复制数据,一个节点出问题时,其余节点可以顶上

什么是RESTful?

Resful是一种软件架构风格,它的全称是Representational State Transfer,意思是表现层状态转换。它的核心思想是把网络上的数据和资源用统一的接口来操作,通过不同的HTTP方法(GET, POST, PUT, DELETE等)来实现对资源的增删改查。Resful API就是遵循Resful风格设计的网络接口,它通常用URL来表示资源,用HTTP方法来表示操作,用JSON或XML等格式来传输数据

URLMethod说明
http://localhost/usersGET获取所有用户列表。GET方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据。
http://localhost/users/1GET获取id为1的用户信息。
http://localhost/usersPOST创建一个新用户。POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
http://localhost/users/1PUT更新id为1的用户信息。PUT方法用有效载荷请求替换目标资源的所有当前表示。
http://localhost/users/1DELETE删除id为1的用户。DELETE方法删除指定的资源。
http://localhost/users/1HEAD获取id为1的用户的标头信息。HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体。这样可以节省带宽和服务器资源,也可以用来检查资源是否存在,或者获取资源的元数据。

如何在ES中实现索引的增删改查

ES的服务端采用RESTful风格进行API交互,在使用前我们最好安装Postman来简化操作,这里我们直接在windows上运行ES程序,首先,启动Es的服务器程序,双击bin/elasticsearch.bat运行即可

  1. 新增job索引
PUT http://localhost:9200/job
{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "job"
}
  1. 查询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"
            }
        }
    }
}
  1. 查询所有索引元数据
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"
            }
        }
    }
}
  1. 查询索引的摘要信息
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:存在问题
statusopen:打开 close:关闭
index索引名
uuid唯一标识
pri主分片数量
rep副本数量
doc.count文档(记录)总数
doc.deleted被删除的文档总量
store.size总占用空间 = 主分片+副本
pri.store.size主分片占用的数据总量
  1. 启用/关闭索引
POST http://localhost:9200/job/_close
POST http://localhost:9200/job/_open
  1. 删除索引
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:ssyyyy‐MM‐ddepoch_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

如何实现已有映射字段的修改调整?

  1. 创建一个全新的索引(index),映射包含调整后的字段或类型 # job2
  2. 将原有索引下的数据迁移(reindex)到新的索引 #job reindex -> #job2
  3. 删除原有索引 #DELETE job
  4. 将新的索引的别名(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

文档编号分词
doc1nice
doc1to
doc1meet
doc1you

doc2:Nice day

文档编号分词
doc2nice
doc2day

倒排索引(从词到文档)

分词文档编号:词频TF:出现位置POS:<偏移量OFFSET>
nicedoc1:1:0:<0,4>,doc2:1:0:<0,4>
todoc1:1:1:<5,7>
meetdoc1:1:2:<8,14>
youdoc1:1:3:<15,18>
daydoc2: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文档110
Elasticsearch文档210
Elasticsearch文档310
数据文档100.48
数据文档210.48
数据文档300.48
Lucene文档100.48
Lucene文档200.48
Lucene文档310.48

然后,我们将TF和IDF相乘得到TF-IDF值,得到以下结果:

词语文档TF-IDF值
Elasticsearch文档10
Elasticsearch文档20
Elasticsearch文档30
数据文档10
数据文档20.48
数据文档30
Lucene文档10
Lucene文档20
Lucene文档30.48

最后,我们将每个文档中的所有词语的TF-IDF值相加得到文档的相关性得分,得到以下结果:

文档相关性得分
文档10
文档20.48
文档30.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)的功能,类似于使用浏览器的时候,搜索提示功能,用户每输入一个字符,就需要即时发送一个查询请求到后段查找匹配项

案例演示:

  1. 重新建立job索引
DELETE http://localhost:9200/job
PUT http://localhost:9200/job
  1. 添加mapping映射
{
  "properties": {
    "jid": {
      "type": "long" // 长整型数字,表示某个职位的编号
    },
    "title": {
      "type": "text" // 文本类型,表示某个职位的标题
    },
    "title_completion": {
      "type": "completion" // 补全类型,表示某个职位的标题可以用来做自动补全功能
    },
    "salary_text": {
      "type": "text", // 文本类型,表示某个职位的薪资
      "index": false // 不用于索引
    },
    "city": {
      "type": "keyword" // 关键字类型,表示某个职位所在的城市,关键字不分词
    }
  }
}
  1. 添加素材
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
                    }
                }
            ]
        }
    }
}
最后修改:2024 年 01 月 17 日
如果觉得我的文章对你有用,请随意赞赏