ElasticSearch基本操作

Elasticsearch是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。

ES安装

准备工作

安装ES之前确保系统已安装JDK(需要JDK1.8以上版本)

安装及启动

下载ES后,完成解压。解压完后的目录:

1
2
3
4
5
6
7
8
9
10
├─bin
├─config
├─jdk
├─lib
├─logs
├─modules
├─plugins
├─LICENSE.txt
├─NOTICE.txt
└─README.asciidoc

双击bin目录下的elasticsearch.bat即可启动ES,默认启动后占用9200端口。

然后可通过http://127.0.0.1:9200/访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
name: "LSW",
cluster_name: "elasticsearch",
cluster_uuid: "DMAfqBqjSAm66iH6nGTeCg",
version: {
number: "7.10.0",
build_flavor: "default",
build_type: "zip",
build_hash: "51e9d6f22758d0374a0f3f5c6e8f3a7997850f96",
build_date: "2020-11-09T21:30:33.964949Z",
build_snapshot: false,
lucene_version: "8.7.0",
minimum_wire_compatibility_version: "6.8.0",
minimum_index_compatibility_version: "6.0.0-beta1"
},
tagline: "You Know, for Search"
}

version中的number即为版本号,这里是7.10.0。

安装ik分词器

在ES目录中的 \plugins 目录下新建 ik 目录,然后将下载完成的 elasticsearch-analysis-ik 解压至此目录下。

完成后重启ES,即可应用ik分词器。

安装可视化工具kibana

下载解压kibana,需修改kibana中config目录下的kibana.yml文件,添加安装的es地址,即:elasticsearch.url: "http://127.0.0.1:9200"

在bin目录下,双击kibana.bat,启动Kibana。

启动后,通过浏览器访问http://127.0.0.1:5601即可访问kibana。

通过侧边菜单【Dev Tools】进行执行命令

ES的基本概念

集群和节点

一个ES集群是由一个或多和ES节点组成的集合,每个节点都有自己的名字,如之前的master、slave1、slave2,节点是可以存储数据,参与索引数据等的独立服务。

索引(类似于数据库里面的database)

索引是含有相同属性的文档集合,索引在ES中是通过一个名字来识别的,必须是英文字母小写,且不含中划线。

索引模板

索引模板是一种复用机制,当新建一个ES索引时,自动匹配模板,完成索引的基础部分搭建。

模板定义:

1
2
3
4
5
6
7
{
"order": 0, // 模板优先级
"template": "sample_info*", // 模板匹配的名称方式
"settings": {...}, // 索引设置
"mappings": {...}, // 索引中各字段的映射定义
"aliases": {...} // 索引的别名
}

主要内容:

  1. settings:指定index的配置信息,比如分片数、副本数、tranlog同步条件、refresh策略等信息
  2. mappings:指定index的内部构建信息,主要有:
    • _all:All Field字段,如果开启_all字段就会把所有字段的内容都包含进来,检索的时候可以不用指定字段查询,设置方式: "_all": {"enabled": true},在ES 6.0开始,_all字段被禁用了,作为替换,可以通过copy_to自定义实现all字段的功能
    • _source:Source Field字段,ES为每个文档都保存一份源数据,如果不开启,也就是"_source": {"enabled": false},查询的时候就只会返回文档的ID,其他的文档内容需要通过Fields字段到索引中再次获取,效率很低;但若开启,索引的体积会更大,此时就可以通过Compress进行压缩,并通过inclueds、excludes等方式在field上进行限制(定义允许哪些字段存储到_source中,哪些不存储)
    • properties:最重要的配置,是对索引结构和文档字段的设置
  3. template:定义该索引模板所应用的索引情况。如 "template": "tete*"表示当新建索引时,所有以tete开头的索引都会自动匹配到该索引模板。利用该模板进行相应的设置和字段添加等

下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
PUT _template/shop_template
{
"index_patterns": ["shop*", "bar*"], // 可以通过"shop*"和"bar*"来适配, template字段已过期
"order": 0, // 模板的权重, 多个模板的时候优先匹配用, 值越大, 权重越高
"settings": {
"number_of_shards": 1 // 分片数量, 可以定义其他配置项
},
"aliases": {
"alias_1": {} // 索引对应的别名
},
"mappings": {
// ES 6.0开始只支持一种type, 名称为“_doc”
"_doc": {
"_source": { // 是否保存字段的原始值
"enabled": false
},
"properties": { // 字段的映射
"@timestamp": { // 具体的字段映射
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"@version": {
"doc_values": true,
"index": "false", // 设置为false, 不索引
"type": "text" // text类型
},
"logLevel": {
"type": "long"
}
}
}
}
}

索引模板使用建议:

  1. 如果我们的数据是结构化数据,就设置"dynamic": "strict",这样可以把动态类型判断设置为严格,也就是不允许ES为插入的数据进行动态类型设置,避免注入脏数据
  2. 如果我们只关心精确匹配,就设置test_field: {"type": "keyword"},因为keyword类型要比text类型的性能更高,并且还能节省磁盘的存储空间

字段映射详解

索引模板中,映射字段所对应的常用结构是:

1
2
3
4
5
6
"mappings": {
"my_type": { // 索引下的类型 my_type 应用该映射
"dynamic_templates": [ ... ], // 动态映射部分,用于未定义的 my_type 下字段
"properties": { ... } // 自定义字段的响应映射
}
}

如果采用上述定义的索引模板,则索引下仅仅my_type类型应用了该索引模板。如果想要索引下的所有模板应用定义的映射,则可以将my_type替换成default字段。

动态映射

动态映射dynamic_templates字段对应的是一个数组,数组中的元素是一个个字段的映射模板。每个字段的映射模板都有一个名字用户描述这个模板的用途,一个 mapping 字段指明这个映射如何使用,和至少一个参数(例如 match)来定义这个模板适用于哪个字段。

dynamic_templates字段对应的字段模板结构如下:

1
2
3
4
5
6
{
"string_fields": { // 字段映射模板的名称,一般为"类型_fields"的命名方式
"match": "*", // 匹配的字段名为所有
"match_mapping_type": "string", // 限制匹配的字段类型,只能是 string 类型
"mapping": { ... } // 字段的处理方式
}

一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

"mappings": {
"my_type": {
"dynamic_templates": [
{
"string_fields": { // 字段映射模板的名称,一般为"类型_fields"的命名方式
"match": "*", // 匹配的字段名为所有
"match_mapping_type": "string", // 限制匹配的字段类型,只能是 string 类型
"mapping": {
"fielddata": { "format": "disabled" }, // fielddata 不可用,对于分析字段,其默认值是可用
"analyzer": "only_words_analyzer", // 字段采用的分析器名,默认值为 standard 分析器
"index": "analyzed", // 索引方式定义为索引,默认值是分析
"omit_norms": true, // omit_norms 为真表示考虑字段的加权,可分析字段默认值 false
"type": "string", // 字段类型限定为 string
"fields": { // 定义一个嵌套字段,将该字段应用于不分析的场景
"raw": {
"ignore_above": 256, // 忽略字段对应的值长度大于256的字段
"index": "not_analyzed", // 索引方式为不分析
"type": "string", // 字段的类型为 string
"doc_values": true // 对于不分析字段,doc_values 对应的是一种列式存储结构,默认false
}
}
}
}
},
...
],
"properties": { ... }
}
}
  • fielddata 这一字段就是对于分析字段,除了倒排索引的数据存储方式,仍定义一种形式上看着是行式结构的存储样式,数据存储在内存中,用于对字段的排序和聚合分析。对于分析字段 fielddata 的默认值是可用的。如果确定一个分析字段不需要排序或者聚合分析,则该字段就设置为不可用。这样可以节省内存。
  • analyzer 定义的是该字段的分析器,默认的分析器是 standard 标准分析器,这个地方可定义为自定义的分析器。
  • index 定义的是字段的索引方式。默认的 ,Elasticsearch 对字段采取的是分析索引,即会对字段进行倒排索引。
  • omit_norms 定义的是字段在分析过程中,是否考虑加权。例如如果权重为2,字段出现的频率在计算时会翻倍,以提高计算的匹配度。对于分析字段,默认的是不考虑加权。
  • type 定义的是字段的数据类型。
  • fields 是字段定义一个嵌入字段。以 title 字段为例,Elasticsearch 会自动生成两个字段,分别是 title 和 title.raw 。这样,在有可能同时需要分词和不分词结果的环境,就可以很灵活的使用不同的索引字段。比如,查看标题中最常用的单词,应该是使用 title 字段,查看阅读数最多的文章标题,应该是使用 title.raw 字段。
    • ignore_above 字段是索引时忽略长度超过定义值的字段。
    • doc_values 字段与 fielddata 相对应,都是为了字段方便排序和聚合分析而重新定义的一种数据存储方式,不同的是,fielddata 应用于分析字段,存储在内存中;doc_values 应用于不分析字段,存储于磁盘中。

上述是针对字段数据类型为 string 的动态模板定义,对于其他数据类型,其动态模板的定义方式一般如下:

1
2
3
4
5
6
7
8
9
10
{
"float_fields": { // 命名方式 "类型_fields"
"match": "*",
"match_mapping_type": "float",
"mapping": {
"type": "float",
"doc_values": true // doc_values 定义为 true,便于排序与聚合分析
}
}
}
定义字段映射

针对索引类型中存在的字段,除了可以采用动态模板的方式,还可以采用定义定义的方式,常见的自定义结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"mappings": {
"my_type": {
"dynamic_templates": [ ... ],
"properties": {
"user_city": { // 字段名
"analyzer": "lowercase_analyzer", // 字段分析器
"index": "analyzed", // 字段索引方式定义索引
"type": "string", // 字段数据类型定义为 string
"fields": { // 定义一个名为 user_city.raw 的嵌入的不分析字段
"raw": {
"ignore_above": 512,
"index": "not_analyzed",
"type": "string"
}
}
},
"money":{
"type": "double",
"doc_values": true
},
...
}
}
}

类型(类似于sql中的table,存在一定区别)

一个索引可以定义一个或多个类型,文档必须属于一个类型。

注意:随着 7.0 版本的即将发布,type 的移除也是越来越近了,在 6.0 的时候,已经默认只能支持一个索引一个 type 了,7.0 版本新增了一个参数 include_type_name ,即让所有的 API 是 type 相关的,这个参数在 7.0 默认是 true,不过在 8.0 的时候,会默认改成 false,也就是不包含 type 信息了,这个是 type 用于移除的一个开关。

为什么要取消类型(type)的概念

我们一直认为ES中的index类似于关系型数据库的database,而type相当于一个数据表。这是一个糟糕的认识。

例如:关系型数据库中两个数据表是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的,ES是基于Lucene开发的搜索引擎,不同type下名称相同的filed最终在Lucene中的处理方式是一样的。举个例子,两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。

去掉type能够使数据存储在独立的index中,这样即使有相同的字段名称也不会出现冲突,就像ES出现的第一句话一样“你知道的,为了搜索····”,去掉type就是为了提高ES处理数据的效率。除此之外,在同一个索引的不同type下存储字段数不一样的实体会导致存储中出现稀疏数据,影响Lucene压缩文档的能力,导致ES查询效率的降低。

文档(相当于sql中的一行记录)

文档是可以被索引的基本数据单位。

分片

每个索引都有多个分片,每个分片都是一个luncene索引。

分片的好处:分摊索引的搜索压力,分片还支持水平的拓展和拆分以及分布式的操作,可以提高搜索和其他处理的效率。

备份

拷贝一个分片就完成了分片的备份。

备份的好处:当主分片失败或者挂掉,备份就可以代替分片进行操作,进而提高了es的可用性,备份的分片还可以进行搜索操作,以分摊搜索的压力。

ES在创建索引时,默认创建5个分片,一份备份,可以修改,分片的数量只能在创建索引的时候指定,索引创建后就不能修改分片的数量了,而备份是可以动态修改的。

ES常用字段类型

ES常用的数据类型可分为3大类:

  • 核⼼数据类型
  • 复杂数据类型
  • 专⽤数据类型

核心数据类型

字符串

  • text:⽤于全⽂索引,搜索时会自动使用分词器进⾏分词再匹配(当一个字段需要用于全文搜索(会被分词),比如产品名称、产品描述信息,就应该使用text类型
  • keyword:不分词,搜索时需要匹配完整的值(当一个字段需要按照精确值进行过滤、排序、聚合等操作时,就应该使用keyword类型

数值型

  • 整型:byteshortintegerlong
  • 浮点型:floathalf_floatscaled_floatdouble
类型 说明
byte 有符号的8位整数,范围: [-128 ~ 127]
short 有符号的16位整数,范围: [-32768 ~ 32767]
integer 有符号的32位整数,范围: [−2^31 ~ 2^31-1]
long 有符号的64位整数,范围: [−2^63 ~ 2^63-1]
float 32位单精度浮点数
double 64位双精度浮点数
half_float 16位半精度IEEE 754浮点类型
scaled_float 缩放类型的的浮点数,比如price字段只需精确到分,57.34缩放因子为100,存储结果为5734

注意事项:尽可能选择范围小的数据类型,字段的长度越短,索引和搜索的效率越高,优先考虑使用带缩放因子的浮点类型

日期类型-date

注意:JSON没有date类型,插入、更新文档/字段时表示date类型有以下几种方法:

  1. 使用固定格式的字符串:”2021-04-18”、”2021/04/18 09:00:00”
  2. 值使用长整型的时间戳(s):1610350870
  3. 值使用长整型的时间戳(ms):1641886870000

范围型-range

类型 范围
integer_range −2^31 ~ 2^31−1
long_range −2^63 ~ 2^63−1
float_range 32位单精度浮点型
double_range 64位双精度浮点型
date_range 64位整数,毫秒计时
ip_range IP值的范围,支持IPV4和IPV6,或者这两种同时存在

布尔-boolean

⼆进制-binary

⼆进制会把值当做经过 base64 编码的字符串,默认不存储,且不可搜索

添加映射:

1
2
3
4
5
6
7
8
9
10
PUT website
{
"mappings": {
"blog": {
"properties": {
"blob": {"type": "binary"} // 二进制
}
}
}
}

添加数据:

1
2
3
4
5
PUT website/blog/1
{
"title": "Some binary blog",
"blob": "hED903KSrA084fRiD5JLgY=="
}

复杂数据类型

数组类型-array

ES中没有专门的数组类型,直接使用[]定义即可。

注意:数组中所有的值必须是同一种数据类型,不支持混合数据类型的数组

  • 字符串数组: ["one", "two"]
  • 整数数组: [1, 2]
  • 对象数组: [{"name": "Tom", "age": 20}, {"name": "Jerry", "age": 18}]

对象类型-object

JSON文档是分层的:文档可以包含内部对象,内部对象也可以包含内部对象。

添加数据:

1
2
3
4
5
6
7
8
PUT employee/developer/1
{
"name": "ma_shoufeng",
"address": {
"region": "China",
"location": {"province": "GuangDong", "city": "GuangZhou"}
}
}

文档的映射结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PUT employee
{
"mappings": {
"developer": {
"properties": {
"name": { "type": "text", "index": "true" },
"address": {
"properties": {
"region": { "type": "keyword", "index": "true" },
"location": {
"properties": {
"province": { "type": "keyword", "index": "true" },
"city": { "type": "keyword", "index": "true" }
}
}
}
}
}
}
}
}

嵌套类型-nested

嵌套类型是对象数据类型的一个特例,可以让array类型的对象被独立索引和搜索

对象数组存储:

  1. 添加数据
1
2
3
4
5
6
7
8
PUT game_of_thrones/role/1
{
"group": "stark",
"performer": [
{"first": "John", "last": "Snow"},
{"first": "Sansa", "last": "Stark"}
]
}
  1. 内部存储结构
1
2
3
4
5
{
"group": "stark",
"performer.first": [ "john", "sansa" ],
"performer.last": [ "snow", "stark" ]
}

可以看出,user.firstuser.last会被平铺为多值字段,这样一来,John和Snow之间的关联性就丢失了。在查询时可能出现John Stark的结果。

我们可以用nested类型解决object类型的不足。嵌套对象实质是将每个对象分离出来,作为隐藏文档进行索引。

创建映射:

1
2
3
4
5
6
7
8
9
10
PUT game_of_thrones
{
"mappings": {
"role": {
"properties": {
"performer": {"type": "nested" }
}
}
}
}

检索数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET game_of_thrones/_search
{
"query": {
"nested": {
"path": "performer",
"query": {
"bool": {
"must": [
{ "match": { "performer.first": "John" }},
{ "match": { "performer.last": "Snow" }}
]
}
},
"inner_hits": {
"highlight": {
"fields": {"performer.first": {}}
}
}
}
}
}

ES基本操作-Kibana-Dev Tools

GET /

在右侧将看到和启动完ES后在浏览器输入localhost:9200相同的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "LSW",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "DMAfqBqjSAm66iH6nGTeCg",
"version" : {
"number" : "7.10.0",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "51e9d6f22758d0374a0f3f5c6e8f3a7997850f96",
"build_date" : "2020-11-09T21:30:33.964949Z",
"build_snapshot" : false,
"lucene_version" : "8.7.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}

创建索引

1
2
3
4
5
6
POST custinfo/_doc/1
{
"user":"zhangsan",
"age":18,
"city":"深圳"
}

说明:因为7版本之后,ES不再支持一个索引(index)可以创建多个类型(type),所以custinfo后边不再需要写入类型名称,而是统一使用_doc代替即可。

查看刚才创建的索引

1
GET custinfo/_doc/1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"user" : "zhangsan",
"age" : 18,
"city" : "深圳"
}
}
  • _index是刚才创建的索引名称
  • _type是类型,7版本统一为_doc
  • _id为创建时的ID,如果创建索引的时候不设置ID,那么ES将默认分配一个ID
  • _version为版本号,如果我们之后对该数据进行了修改,那么他会随之变化
  • _source里边就是我们刚才加进去的数据内容

删除索引

1
DELETE custinfo

只需要在DELETE后边加上索引名称即可。

修改数据

1
2
3
4
5
6
7
POST custinfo/_doc/1
{
"user":"zhangsan",
"age":20,
"city":"上海",
"sex":"m"
}

这里我们修改了age、city值,并添加了一个新属性sex,执行之后我们再次执行获取数据内容命令GET custinfo/_doc/1,如下,可以看到数据已经被修改,版本号变成了2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"user" : "zhangsan",
"age" : 20,
"city" : "上海",
"sex" : "m"
}
}

bulk方法批量插入数据

使用POST方法,然后每一条数据的格式是一致的,首先第一行输入{"index":{"_index":"custinfo"}},也就是索引名称,第二行输入要插入的完整数据,这里特别提醒下,插入的这条数据不能使用刚才创建数据时的那种多行形式,只能使用没有回车的一条数据,否则会报错。

1
2
3
4
5
6
7
POST _bulk
{"index":{"_index":"custinfo"}}
{"user":"zhangsan","age":20,"city":"上海","sex":"m"}
{"index":{"_index":"custinfo"}}
{"user":"lisi","age":22,"city":"上海","sex":"m"}
{"index":{"_index":"custinfo"}}
{"user":"wangwu","age":22,"city":"深圳","sex":"f"}

执行完毕后,我们再次获取数据看一下:

1
GET custinfo/_search
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"user" : "zhangsan",
"age" : 20,
"city" : "上海",
"sex" : "m"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "N0PxxnUBfXI6F1bGW_pJ",
"_score" : 1.0,
"_source" : {
"user" : "zhangsan",
"age" : 20,
"city" : "上海",
"sex" : "m"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "OEPxxnUBfXI6F1bGW_pJ",
"_score" : 1.0,
"_source" : {
"user" : "lisi",
"age" : 22,
"city" : "上海",
"sex" : "m"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "OUPxxnUBfXI6F1bGW_pJ",
"_score" : 1.0,
"_source" : {
"user" : "wangwu",
"age" : 22,
"city" : "深圳",
"sex" : "f"
}
}
]
}
}

按照条件查询

这里主要使用的是Bool Query,它使用一个或者多个布尔条件来构建,每个条件都有一个类型出现,出现的类型有:

事件 描述
must 子句(查询)必须出现在匹配的文档中,并将有助于得分。
filter 子句(查询)必须出现在匹配的文档中。 然而不像 must 此查询的分数将被忽略。
should 子句(查询)应出现在匹配文档中。 在布尔查询中不包含 must 或 filter 子句,一个或多个should 子句必须有相匹配的文件。 匹配 should 条件的最小数目可通过设置minimum_should_match 参数。
must_not 子句(查询)不能出现在匹配的文档中。

基本查询

1
2
3
4
5
6
7
8
GET custinfo/_search
{
"query": {
"match": {
"city": "深圳"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 2.4079456,
"hits" : [
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "OUPxxnUBfXI6F1bGW_pJ",
"_score" : 2.4079456,
"_source" : {
"user" : "wangwu",
"age" : 22,
"city" : "深圳",
"sex" : "f"
}
}
]
}
}

当同一个属性满足逻辑或时的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET custinfo/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"city": "深圳"
}
},
{
"match": {
"city": "上海"
}
}
]
}
}
}

这里是查询属性city等于上海或者深圳的数据,其中,固定输入”query”,固定输入”bool”,输入为”should”,表示是逻辑或的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 2.4079456,
"hits" : [
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "OUPxxnUBfXI6F1bGW_pJ",
"_score" : 2.4079456,
"_source" : {
"user" : "wangwu",
"age" : 22,
"city" : "深圳",
"sex" : "f"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.7133499,
"_source" : {
"user" : "zhangsan",
"age" : 20,
"city" : "上海",
"sex" : "m"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "N0PxxnUBfXI6F1bGW_pJ",
"_score" : 0.7133499,
"_source" : {
"user" : "zhangsan",
"age" : 20,
"city" : "上海",
"sex" : "m"
}
},
{
"_index" : "custinfo",
"_type" : "_doc",
"_id" : "OEPxxnUBfXI6F1bGW_pJ",
"_score" : 0.7133499,
"_source" : {
"user" : "lisi",
"age" : 22,
"city" : "上海",
"sex" : "m"
}
}
]
}
}

多个条件同时满足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET custinfo/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"age": "22"
}
},
{
"match": {
"city": "上海"
}
}
]
}
}
}

这里和上一条查询的关键区别就在于由”should”改为”must”。

范围查询并进行排序

符号 含义
gte greater-than or equal to,大于或等于
gt greater-than,大于
lte less-than or equal to,小于或等于
lt less-than,小于

数值范围查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET custinfo/_search
{
"query": {
"range": {
"age": {
"gte": 20,
"lte": 21
}
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}

这里,gte和lte表示查询属性age在20-21范围的数据,然后sort表示排序,排序的属性是age,order表示排序为倒序desc。

时间范围查询

1
2
3
4
5
6
7
8
9
10
11
GET custinfo/_search
{
"query": {
"range": {
"post_date": {
"gte": "now-1d/d", // 当前时间的上一天,四舍五入到最近的一天
"lt": "now/d" // 当前时间,四舍五入到最近的一天
}
}
}
}

在日期之后,可以选择一个或多个数学表达式:

  • +1h —— 加1小时
  • -1d —— 减1天
  • /d —— 四舍五入到最近的一天

说明:假设系统当前时间now = 2018-10-01 12:00:00

  • now-1h/d:now的毫秒值 - 1小时,然后四舍五入到最近的一天的起始,结果是2018-10-01 00:00:00
  • 2018.10.01||+1M/d:2018-10-01的毫秒值 + 1月,再四舍五入到最近一天的起始,结果是2018-11-01 00:00:00

日期格式化范围查询(format)

1
2
3
4
5
6
7
8
9
10
11
12
GET custinfo/_search
{
"query": {
"range": {
"post_date": {
"gte": "2/1/2018",
"lt": "2019",
"format": "dd/MM/yyyy||yyyy"
}
}
}
}

如果日期中缺失了部分年、月、日,缺失的部分将被填充为unix系统的初始值(也就是1970年1月1日)。比如,将dd指定为format,像"gte": 10将转换为1970-01-10T00:00:00.000Z

注意:"format": "yyyy-MM-dd HH:mm:ss",这里HH表示24小时制的小时,hh表示12小时制的小时

聚合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET custinfo/_search
{
"size": 0,
"aggs": {
"age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 21
},{
"from": 22,
"to": 25
},{
"from": 25,
"to": 30
}
]
}
}
}
}

这条请求实现的是统计属性”age”按照20-21,22-25,25-30划分的数据条数分别为多少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"age" : {
"buckets" : [
{
"key" : "20.0-21.0",
"from" : 20.0,
"to" : 21.0,
"doc_count" : 2
},
{
"key" : "22.0-25.0",
"from" : 22.0,
"to" : 25.0,
"doc_count" : 2
},
{
"key" : "25.0-30.0",
"from" : 25.0,
"to" : 30.0,
"doc_count" : 0
}
]
}
}
}

参考


----------- 本文结束啦感谢您阅读 -----------

赞赏一杯咖啡

欢迎关注我的其它发布渠道