Elastic Stack - Elasticsearch · 倒排索引 · 字段数据类型 · 增删改查 · 父子关系 · SQL语句

系列目录
Elastic Stack - Elasticsearch · 搜索引擎 · 整体介绍 · 部署应用 · 内部结构 · 服务接入
Elastic Stack - Elasticsearch · 倒排索引 · 字段数据类型 · 增删改查 · 父子关系 · SQL语句
Elastic Stack - Kibana (待续)
Elastic Stack - Logstash (待续)
Elastic Stack - Beats (待续)
Elastic Stack - Application Performance Monitoring (待续)

  我看过一本较早的书,并非所有相关人员都能看的懂的书,我也是很少接触这方面应用的人员,对我来说算初学者,所以我要搞懂的地方还有很多,我也不打算去全面的搞懂它,为此,我写的文章为方便以后我查看,当然,能方便大家也最好。

所以,我挑了一些章节按我的说法或原文写下来。所以,望多多包含。

一、倒排索引

  在 Elasticsearch 中索引的概念与关系型数据库中的索引不尽相同,Elasticsearch 中的索引是倒排索引(Inverted Index),是一种应用于全文检索的索引类型,于此类似的还有映射、类型、文档、字段等诸多概念。

先来解释一下什么叫全文检索。数据检索的目的是从一系列数据中,根据某一或某些数据特征将特定的数据找出来。从数据检索的角度来看,数据大体上可以分为两种类型:一种是结构化数据;另一种是非结构化数据。结构化数据将数据具有的特征事先以结构化的形式定义好,数据有固定的格式或有限的长度。典型的结构化数据就是传统关系型数据库的表结构,数据特征直接体现在表结构的字段上,所以根据某一特征做数据检索很直接,速度也比较快。相比结构化数据,非结构化数据的检索要难得的。在对它们的检索中,像文章、网页、邮件这种全文本(Full-text)数据的检索需求占了大多数,而且与图片、视频等非文本数据的检索完全不同,因此形成了一门独立的学科,这就是全文检索。

关系型数据库提升数据查询速度的常用方法是给字段添加索引,有了索引的字段会根据字段值排序并创建类似排序二叉树的数据结构,这样就可以利用二分查找等算法提升查询速度。所以在字段添加索引后,通过这些字段做查询时速度能够得到非常明显的提升。但由于添加索引后需要对字段排序,所以增加和删除数据时速度会变慢,并且还需要额外的空间存储索引。这是典型的利用空间换取时间的策略。

倒排索引是先将文档中包含的关键字全部提取出来,然后再将关键字与文档的对应关系保存起来,最后再对关键字本身做索引排序。用户在检索某一关键字时,可以先对关键字的索引进行查找,再通过关键字与文档的对应关系找到所在文档。

关系型数据库索引一般是对主键创建,然后索引指向数据内容;而倒排索引则正好相反,它是针对文档内容创建索引,然后索引指向主键,这就是这种索引被称为倒排索引的原因。

在全文数据库中,文档在插入时还不是结构化的,需要应用程序根据规则自动提取关键字,并形成关键字与文档之间的结构化对应关系。由于文档在创建时需要提取关键字并创建索引,所以向全文数据库添加文档比关系型数据库要慢一些。

文档的词项提取在 Elasticsearch 中称为文档分析(Analysis),是整个全文检索中较为核心的过程。这个过程必须要区分哪些是词项,哪些不是。

在 Elasticsearch 中存储文档最好预先创建索引,尽管这并不是必须的。用户预先创建索引可以指明文档存储时怎么分词,如何创建索引等重要配置信息,这些对于提升检索速度显然是有益的。

因为文档存储前的分析和索引过程比较耗资源,所以为了提升性能,文档在添加到 Elasticsearch 中时并不会立即被编入索引。在默认情况下,Elasticsearch 会每隔 1s 统一处理一次新加入的文档,可以通过index.refresh_interval参数修改。为了提升性能,在 Elasticsearch 中还添加了index.search.idle.after参数,它的默认值是 30s。其大体含义是,如果索引在一段时间内没有收到检索数据的请求,那么它至少要等 30s 后才会刷新索引数据。

二、字段数据类型

2.1 字符串类型

text:长字符串类型,倒排索引,会做词项分析,词频,词序等信息,如日志、文章、邮件等。

keyword:短字符串类型,用于存储结构化的文本数据,以整个字符串对比,如邮编、地址、电话等。

{字段名text}.keyword:一个字段,两个类型的合并使用,包括全字匹配和分词处理。如 title 等。

2.2 数值类型

类型名称 值范围 说明
long -263 ~ (263 - 1) 64位整型数据
integer -231 ~ (231 - 1) 32位整型数据
short -32768 ~ 32767 16位整型数据
byte -128 ~ 127 8位整型数据
double 参照 IEEE 754 双精度浮点数,占 64 位
float 参照 IEEE 754 单精度浮点数,占 32 位
half_float 参照 IEEE 754 半精度浮点数,占 16 位
scaled_float - 基于 long 类型表示的浮点数,值取决于scaling_factor

2.3 日期类型

Elasticsearch 有两种日期类型,分别是date和date_nanos。默认情况下,两者都支持类似于 "yyyy-MM-ddTHH:mm:ss" 的格式和1970年之后到如今的长整型毫秒数。

复制代码
# 插入数据
PUT testindex1/_doc/1
{
    "date_1" : "2025-01-01T10:30:00"
}

不过date_nanos通过format设置为strict_date_optional_time_nanos,以支持一个日期显示的纳秒部分。其实它支持精确到纳秒。

复制代码
# 索引,字段定义
PUT testindex2/_mapping
{
  "properties": {
      "date_2": {
        "type": "date_nanos",
        "format" : "strict_date_optional_time_nanos"
      }
    }
}
# 插入数据
PUT testindex2/_doc/1
{
    "date_2": "2025-01-01T10:30:00.382719622Z"
}

2.4 布尔类型

布尔类型的关键字是boolean,两个值既 true 和 false,也可以使用字符串形式 "true" 和 "false"。

2.5 字节类型

字节类型接收以 Base64 编码后表示的二进制字节流,所以尽管它的类型是字节类型,但在文档中它表现出来的仍然是字符串。字节类型的字段在默认情况下不会被存储,也不会被检索到。

2.6 范围类型

范围类型要求字段的值描述的是一个数值、日期或IP地址的范围,添加文档时可以使用gte、gt、lt、lte分别表示大于等于、大于、小于、小于等于。数值范围类型包括 integer_range、float_range、long_range、double_range,日期范围类型和 IP 范围类型分别为 date_range 和 ip_range。如下:

复制代码
# 索引,字段定义
PUT index3
{
    "mappings": {
        "properties": {
            "age_range_1": {
                "type": "integer_range"
            }
        }
    }
}
# 插入数据
POST index3/_doc
{
    "age_range_1": {
        "lt": 78,
        "gte": 13
    }
}

2.7 数组

使用[]来确认该字段为数组。如下:

复制代码
# 插入数据
PUT person1/doc/1
{
    "relation_1": ["15", "36"]
}

2.8 对象

使用{}来确认该字段为对象。如下 obj-address 的 city 和 country。

复制代码
# 索引,文档定义
PUT person1
{
    "mappings": {
        "propertions": {
            "obj-address": {
                "propertions": {
                    "city": { "type": "keyword" },
                    "country": { "type": "keyword" }
                }
            }
        }
    }
}

2.9 多数据类型

针对字符串类型text和keyword,假如一个 title 字段,如果内容长了想做词项检索的方式,如果内容短了想整个标题做检索。所以 Elasticsearch 提供了一个用于配置字段多数据类型的参数fields,它能让一个字段同时具备两种数据类型的特征。如下将 articles 索引的 title 字段设置为两种数据类型:

复制代码
# 索引,文档定义
PUT articles
{
    "mappings": {
        "properties": {
            "title_1": {
                "type": "text",
                "fields": { "raw": { "type": "keyword" }},
                "length": { "type": "token_count", "analyzer": "index1"}
            }
        }
    }
}

如上述示例,首先默认为text类型,其次再考虑keyword类型。

三、索引的操作

3.1 创建索引

简单,一个请求就好。

复制代码
# 建 students 索引
PUT /students
# 建 my-index 索引
PUT /my-index
# 检查一个索引是否存在
HEAD /my-index-000001

不过,还有更复杂的方式。

复制代码
PUT /my-index-000001
{
    # 参数设置(集群使用)
    "settings": {
        "number_of_shards": 3,    # 默认为 1 的分片
        "number_of_replicas": 2   # 默认为 1 的副本
    },
    # 字段类型设置(映射)
    "mappings": {
        "properties": {
            "title": {
                "type": "text",                # title 即 text 类型,可做词项
                "fields": {                    # 又是 keyword 类型,可全字匹配
                    "keyword": {
                        "type": "keyword",
                        "ignore_above": 32     # 不可超过 32 位字符,否则此列不会加入索引
                    }
                }
            },
            "name": { "type": "keyword" },     # keyword 类型,title.keyword,全字匹配
            "age": { "type": "integer" },                               # 数字类型
            "description": { "type": "text" },                          # text类型
            "created_at": { "type": "date", "format": "yyyy-MM-dd" }    # 日期类型
        }
    }
}

以上代码中,在后续数据插入后,长度超出 32 个字符的 title,它的ignore_above就会生效,因此,本笔数据的title.keyword会失效,所以,查找的返回数据中,失效的数据会标识"_ignored": [ "title.keyword" ]。

3.2 查看索引

复制代码
# 查看单个索引
GET students
# 查看多个索引
GET students,my-index
# 查看全部索引
GET *
GET _all
# 包含 index 字符的索引
GET *index*

3.3 复制索引

复制代码
# 将 my_source_index 复制为新的索引 my_target_index
POST /my_source_index/_clone/my_target_index

3.4 打开关闭

将数据持久化到硬盘中,减少内存占用。等再有需要的时候重新打开索引。

复制代码
POST /my-index-000001/_open
POST /my-index-000001/_close

3.5 删除索引

复制代码
DELETE my-index-000001                    # 删除单个
DELETE my-index-000001,my-index-000002    # 删除多个
DELETE _all                               # 删除全部
DELETE index_*                            # 按占位符删除

3.6 清除缓存

复制代码
# 清除所有索引的缓存
POST /_cache/clear
# 清除单个索引的缓存
POST /my-index-000001/_cache/clear
# 清除指定字段的缓存
POST /my-index-000001/_cache/clear?fields=foo,bar
# 清除某类索引的缓存
POST /my-index-000001/_cache/clear?fielddata=true
POST /my-index-000001/_cache/clear?query=true
POST /my-index-000001/_cache/clear?request=true

四、文档的增删改查

4.1 新增

用 POST,在索引 students 中添加文档。

复制代码
POST /students/_doc
{
    "title": "xibanya"
}

用 POST,在索引 students 中添加 _id 为 11x 的文档。

复制代码
POST /students/_create/11x
{
    "title": "xibanya"
}

4.2 修改

用 PUT,在索引 students 中修改 _id 为 10 的文档。

复制代码
PUT /students/_doc/10
{
    "title": "xibanya"
}

当然,修改还有很多种,用 POST 也能修改,只不过有没有幂等性的区别,如下:

复制代码
# 按 _id 修改
POST /my-index/_update/1
{
    "doc": {
        "name": "Fanng",
        "title": "Jhon"
    }
}
# upsert 更新或插入
POST /my-index/_update/3
{
    "doc": {
        "title": "Jaxig"
    },
    # _id 为 3 的数据不存在时的完整插入内容
    "upsert": {
        "name": "Mnndna",
        "title": "Jaxig"
    }
}
# upsert 更新或插入
POST /my-index/_update/5
{
    "doc": {
        "title": "Jaxig"
    },
    # 保持 doc 为插入内容
    "doc_as_upsert": true
}

4.3 删除

单个删除

复制代码
# 按 _id 删除
DELETE /my-index/_doc/8

多个删除

复制代码
# 按 字段值 删除
POST /my-index/_delete_by_query
{"query": {"match": {
    "score":88
}}}

4.4 查询

查询标识 _id 为 1 的文档,如下的各种查询方式:

复制代码
# 查文档,包含 _source
GET /my-index/_doc/1
# 查文档,是否包含源 _source
GET /my-index/_doc/1?_source=true
# 查文档,源 _source 中只包含字段
GET /my-index/_doc/1?_source=title,name
# 查源方式,只返回源
GET /my-index/_source/1
# 查源方式,要求包含与不包含的源 _source
GET /my-index/_source/1/?_source_includes=title&_source_excludes=age
# 取多个索引的文档
GET _mget
{
    "docs":[
        {
            "_index": "my-index",
            "_id": "RYnpraABUXQg44tJVPGi"
        },{
            "_index": "students",
            "_id": "TIk_rqABUXQg44tJ4_Fw"
        }
    ]
}

ℹ️ 简单搜索

复制代码
# 用 q 查询,按文档中的字搜索,按文档中的数值查询,等等查询全部
GET /my-index/_search
GET /my-index/_search?q="上海"
GET /my-index/_search?q=name:张展硕&sort=age:desc

ℹ️ term

term 是代表全字匹配,完全匹配,如下"孟洋"也不行,需要"吕孟洋"才可以,也不进行分词器分析。文档中必须包含整个搜索的词汇。

复制代码
GET /index001/_search
{
    "query": {
        "term": {
            "name": "吕孟洋"
        }
    }
}

ℹ️ terms

多个匹配

复制代码
GET /index001/_search
{
    "query": {
        "terms": {
            "name": ["吕孟洋", "陈妤颉"]
        }
    }
}

ℹ️ match

分词匹配搜索

复制代码
GET /index001/_search
{
    "query": {
        "match": {
            "title": {
                "query": "西班牙 俱乐部",
                "operator": "or"
            }
        }
    }
}

ℹ️ match_all

固定格式,查询全部(应该是开发环境中常用的吧)。

复制代码
GET /index001/_search
{
    "query": {
        "match_all": { }
    }
}

ℹ️ multi_match

多个字段的不同匹配方式。

复制代码
GET /index001/_search
{
    "query": {
        "multi_match": {
            "query": "皇家 Bisected",
            "operator": "and",
            "type": "cross_fields",
            "fields": ["title", "name", "description"]
        }
    }
}

以上中,每条文档的得分方式,取决于 type 的三种方式:best_fields、most_fields、cross_fields。每个文档的得分再按序排列出最终结果,并展现出来。

  • best_fields:默认值,取最大单个字段的分数作为最终得分。也就是在多个字段中取相关性最好的字段。
  • most_fields:取多个字段的得分并累加。
  • cross_fields:先每个字段的得分最大值,再最大值的求和。

ℹ️ match_phrase

查询用于精确匹配短语,要求搜索词条顺序一致且默认连续。也就是说包含以下短语的文档。slop 参数允许在短语匹配中忽略一定的词序偏差,即允许关键词之间有一定的错位。

复制代码
GET /index001/_search
{
    "query": {
        "match_phrase": {
            "description": "Bisected north to south"
        }
    }
}

4.5 批量操作

如果需要批量地对 Elasticsearch 中的文档进行操作,可使用 _bulk 接口执行以提升效率和性能。_bulk 接口一组请求体,请求体一般每两个一组,但对于某些不需要参数的文档操作来说,则可能只有一个请求体。操作类型包括index、create、delete、update等,其中index和create都代表创建文档,区别在于当要创建的文档存在时,create会失败而index则可以变为更新;delete和update则分别代表删除和更新文档。如下:

复制代码
POST _bulk
{"index" : { "_index" : "students", "_id" : "10" } }
{ "name" : "smith" }
{"delete" : {"_index" : "test", "_id" : "5" } }
{"create": {"_index": "test", "_id" : "11" } }
{ "age" : 30, "name": "Candy" }
{ "update" : {"_id" : "1", "_index" : "students"} }
{ "doc" : { "age" : "20"} }

五、模糊查询与分页和排序

5.1 模糊查询

基于词项的查询,也就是针对text类型字段的长文本内容的模糊查询,有一个专门用于模糊查询的类型,这就是 fuzzy 查询。在文档字段中匹配不超过编辑距离的词项。例如:

复制代码
POST /students/_search
{
    "query": {
        "fuzzy": {
            "message": {
                "value": "firefix",
                "fuzziness": 1
            }
        }
    }
}

如上所示,在 message 的字段中包含有词项 firefox,查询条件中给出的词项则为 firefix(两者并不一样,万一是打错了呢)。由于 firefix 到 firefox 的编辑距离为 1 个字母,所以仍然能够将包含有 firefox 词项的文档检索出来。但是如果使用 firefit 作为查询条件,由于编辑距离为 2 则返回结果中将不包含任何文档。

5.2 from/size 参数

_search 接口提供的 from 和 size 两个参数可以实现分页,其中 from 参数代表检索文档的起始位置,默认值为 0;而 size 参数则代表每次检索文档的总量,默认值为 10。form 和 size 即可以在 URI 参数中使用,也可以在请求体中使用。以 DestCountry 为例,如下的两个参数 from/size 都是从第100条文档开始,一共取20条文档:

复制代码
# URL 方式
GET /students/_search?q=DestCountry:CN&from=100&size=20
# 请求体方式
GET /students/_search
{
    "from": 100,
    "size": 20
    "query": {
        "term": {
            "DestCountry": "CN"
        }
    }
}

默认情况下,设置 from/size 数量之和不能超过 10000,否则将会抛出类似 "Result window is too large" 的异常,或者也可以配置index.max_result_window。当然,它有它的保护机制,不配置最好。

5.3 sort 参数

Elasticsearch 的排序机制允许用户在查询结果中按照一个或多个字段的值进行排序。 默认情况下,Elasticsearch 会根据文档的相关性评分(_score)对结果进行排序,但你可以通过指定排序字段来覆盖这一行为。 排序可以应用于数值、日期、字符串等类型的字段,并且支持升序(asc)和降序(desc)两种排序方式。 在 Elasticsearch 中,排序是通过在查询中添加 sort 参数来实现的。

在URL参数中指定排序:

复制代码
GET /my_index/_search?sort=name:asc,description:text
# 此请求将按照 name 字段的值升序排序,如果 name 字段值相同,则按照 description 字段的文本相关性排序。

在请求体中指定排序:

复制代码
POST /my_index/_search
{
    "sort": [
        { "name": { "order": "asc" } },
        { "description": { "order": "text" } }
    ]
}
# 此请求体结构与URL参数形式等效,但提供了更丰富的排序选项。

按照多字段排序:

复制代码
POST /my_index/_search
{
    "sort": [
        { "rel_date": { "order": "desc" } },
        { "rat_date": { "order": "desc" } }
    ]
}
# 这个查询首先按照 rel_date 字段降序排序,对于 rel_date 相同的文档,再按照 rat_date 字段降序排序。

5.4 缓存机制

缓存的引入使得文档检索性能得到了提升,还记得索引是准实时的吗?索引默认情况下会以每秒一次的频率将文档编入索引,Elasticsearch 会在索引更新的同时让缓存也失效,这就保证了索引数据与缓存数据的一致性。缓存数据容量问题则是通过LRU的方式,将最近最少使用的缓存条目清除。同时,Elasticsearch 还提供了一个 _cache 接口用于主动清理缓存。之所以要提供这个接口,是因为 Elasticsearch 为索引提供了一个主动刷新的接口 _refresh,所以最好在主动刷新索引后再主动清理缓存。

ℹ️ _refresh 接口

_refresh 接口用于主动刷新一个或多个索引,将已经添加的文档编入索引以使它们在检索时可见。在调用该接口时,可以直接调用或与一个或多个索引一起使用,还可以使用 _all 刷新所有索引,所以以下调用都是正确的:

复制代码
GET employee/_refresh
POST _refresh
GET _all/_refresh
POST employee,students/_refresh

ℹ️ _cache 接口

_cache 接口用于主动清理缓存,在调用该接口时需要在 _cache 后附加关键字 clear。_cache 接口可以清理所有缓存,也可以清理某一索引甚至某一字段的缓存,还可以只清理某

一种类型的缓存。例如:

复制代码
POST /employee/_cache/clear?query=true
POST /employee,students/_cache/clear?request=true
POST /students/_cache/clear?fielddata=true&fields=notes

5.5 查看运行状态

除了上述接口以外,Elasticsearch 还提供了一组用于查看索引及分片运行情况的接口,包括 _stat、_shard_stores 和 _segments 等。由于它们往往在性能分析时使用。

ℹ️ _stat 接口

_stats 接口用于查看索引上不同操作的统计数据,可以直接请求也可以与索引名称一起使用。_stats 接口返回的统计数据非常多,如果只对其中某一组统计数据感兴趣,可以在 _stats 接口后附加统计名称。例如以下对 _stats 接口的调用都是正确的:

复制代码
GET _stats
GET _stats/store
GET employee/_stats
GET employee/_stats/fielddata

ℹ️ _shard_stores 和 _segments 接口

_shard_stores 接口用于查询索引分片存储情况,而 _segments 接口则用于查看底层 Lucene 的分段情况。这两个接口都只能通过 GET 方法请求,同时都可以针对一个或多个索引,如下:

复制代码
GET _shard_stores
GET /employee,students/_shard_stores
GET _segments
GET /employee/_segments/

六、数据的父子关系

Elasticsearch 中的父子关系是单个索引内部文档与文档之间的一种关系,父文档与子文档同属一个索引并通过父文档 _id 建立联系,类似于关系型数据库中单表内部行与行之间的自关联。

6.1 join 类型

在 Elasticsearch 中并没有外键的概念,文档之间的父子关系通过给索引定义 join 类型字段实现。例如创建一个员工索引 employees,定义一个 join 类型的 management 字段用于确定员工之间的管理与被管理关系:

复制代码
PUT employees
{
    "mappings": {
        "properties": {
            "management": {
                "type": "join",
                "relations": {
                    "manager" : "member"
                }
            }
        }
    }
}

以上代码中,management 字段的数据类型被定义为 join,同时在该字段的 relations 参数中定义父子关系为 manager 与 member,其中 manager 为父而 member 为子,它们的名称可由用户自定义。文档在父子关系中的地位,是在添加文档时通过 join 类型字段指定的。还是以 employees 索引为例,在向 employees 索引中添加父文档时,应该将 management 字段设置为 manager;而添加子文档时则应该设置为 member。具体如下:

复制代码
PUT /employees/_doc/1
{
    "name": "tom",
    "management": {
        "name" : "manager"
    }
}
PUT /employees/_doc/2?routing=1
{
    "name": "smith",
    "management": {
        "name" : "member",
        "parent": "1"
    }
}
PUT /employees/_doc/3?routing=1
{
    "name" : "john",
    "management": {
        "name" : "member",
        "parent" : "1"
    }
}

在以上示例中,编号为 1 的文档其 management 字段通过 name 参数设置为 manager,即在索引定义父子关系中处于父文档的地位;而编号为 2 和 3 的文档其 management 字段则通过 name 参数设置为 member,并通过 parent 参数指定了它的父文档为编号1的文档。在使用父子关系时,要求父子文档必须要映射到同一分片中,所以在添加子文档时 routing 参数是必须要设置的。显然父子文档在同一分片可以提升在检索时的性能,可在父子关系中使用的查询方法有 has_child、has_parent 和 parent_id 查询,还有 parent 和 children 两种聚集。

6.2 has_child 查询

has_child 查询是根据子文档检索父文档的一种方法,它先根据查询条件将满足条件的子文档检索出来,在最终的结果中会返回具有这些子文档的父文档。例如,如果想检索 smith 的经理是谁,可以按如下请求:

复制代码
POST /employees/_search
{
    "query": {
        "has child": {
            "type": "member",
            "query": {
                "match":{
                    "name": "smith"
                }
            }
        }
    }
}

如上,has_child 查询的 type 参数需要设置为父子关系中子文档的名称 member,这样 has_child 查询父子关系时就限定在这种类型中检索;query 参数则设置了查询子文档的条件,即名称为 smith。最终结果会根据 smith 所在文档,通过 member 对应的父子关系检索它的父文档。

6.3 has_parent 查询

has_parent 查询与 has_child 查询正好相反,是通过父文档检索子文档的一种方法。在执行流程上,has_parent 查询先将满足查询条件的父文档检索出来,但在最终返回的结果中展示的是具有这些父文档的子文档。例如,如果想查看 tom 的所有下属,如下请求:

复制代码
POST /employees/_search
{
    "query": {
        "has_parent": {
            "parent_type": "manager",
            "query": {
                "match": {
                    "name": "tom"
                }
            }
        }
    }
}

has_parent 查询在结构上与 has_child 查询基本相同,只是在指定父子关系时使用的参数是 parent_type 而不是 type。

6.4 parent_id 查询

parent_id 查询与 has_parent 查询的作用相似,都是根据父文档检索子文档。不同的是,has_parent 可以通过 query 参数设置不同的查询条件;而 parent_id 查询则只能通过父文档 _id 做检索。例如,查询 _id 为1的子文档:

复制代码
POST /employees/_search
{
    "query": {
        "parent_id":{
            "type": "member",
            "id" : "1 "
        }
    }
}

以上 has_child、has_parent、parent_id 基本逻辑都是通过子文档检索父文档,或是通过父文档检索子文档。

6.5 children 聚集

如果想通过父文档检索与其关联的所有子文档就可以使用 children 聚集。同样以 employess 索引为例,如果想要查看 tom 的所有下属就可以按示例的方式检索:

复制代码
POST employees/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "name": "tom"
        }
    },
    "aggs": {
        "members": {
            "children": {
                "type": "member"
            },
            "aggs": {
                "member_name": {
                    "terms": {
                        "field": "name.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

以上中,query 参数设置了父文档的查询条件,即名称字段 name 为 tom 的文档;而聚集查询 members 中则使用了 children 聚集将它的子文档检索出来,同时还使用了一个嵌套聚集 member_name 将子文档 name 字段的词项全部展示出来了。

6.6 parent 聚集

parent 聚集与 children 聚集正好相反,它是根据子文档查找父文档,parent 聚集在 Elasticsearch 版本6.6以后才支持。例如通过 name 字段为 smith 的文档,查找该文档的父文档:

复制代码
POST /employees/_search?filter_path=aggregations
{
    "query": {
        "match": {
            "name": "smith"
        }
    },
    "aggs": {
        "who_is_manager": {
            "parent": {
                "type": "member"
            },
            "aggs": {
                "manager_name": {
                    "terms": {
                        "field": "name.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

七、使用 SQL 语句

Elasticsearch 提供了多种执行 SQL 语句的方法,可使用类似 _search 一样的 REST 接口执行也可以通过命令行执行。本小节将只介绍常见的 _sql 接口。

7.1 _sql 接口

Elasticsearch 在版本7以后使用 _sql 接口。例如:

复制代码
# 1、为分页,仅执行一次(仅呈现第一页)
POST _sql?format=json
{
    "query": """
        select id, title, description
        from students
        where id = 'park_rocky'
        order by title desc
    """,
    "fetch_size": 100,                       # 默认1000
    "page_timeout": "45s",                   # 分页超时
    "request_timeout": "90s",                # 请求超时
    "time_zone": "Z"                         # 时区默认
}
# 2、为分页,把以上执行后返回的 cursor 粘贴至此
# 每次把返回的 cursor 粘贴至此,重复执行
# 也就是呈现第 2-end 页,直至结束。(呈现剩下页)
POST _sql?format=json
{
    "cursor": "iM+b3emV9oAa ...... wkxXs7/wMA"
}
# 3、为分页,把最后返回的 cursor 粘贴至此并执行(为 cursor 关闭)
POST _sql/close
{
    "cursor": "iM+b3emV9oAa ...... wkxXs7/wMA"
}

以上示例中,_sql 接口通过 query 参数接收 SQL 语句,而 SQL 语句也包含有 select、from、where、order by 等子句。_sql 接口的 URL 请求参数 format 定义了返回结果格式,也可以通过在调用 _sql 接口时的参数设置返回结果的格式。比如在以上例子中定义了返回结果格式为 json。除了 json 以外,_sql 接口还支持 csv、json、tsv、txt、yaml、cbor、smile 格式。其中,cbor 和 smile 是两种二进制格式,适用于通过程序解析的应用场景。

7.2 SQL 语法

Elasticsearch 支持传统关系型数据库 SQL 语句中的查询语句,但并不支持 DML、DCL 语句。换句话说,它只支持 SELECT 语句,不支持 INSERT、UPDATE、DELETE 语句。除了 SELECT 语句以外,Elasticsearch 还支持 DESCRIBE 和 SHOW 语句。

ℹ️ SELECT 语句

SELECT 语句用于查询文档,基本语法格式如下:

复制代码
SELECT select_expr [, ...]
[ FROM table_name ]
[ WHERE condition ]
[ GROUP BY grouping_element [, ...] ]
[ HAVING condition]
[ ORDER BY expression [ ASC | DESC ] [, ...] ]
[ LIMIT [ count ] ]

通过以上可以看出,Elasticsearch 的 SELECT 语句跟普通 SQL 几乎没有什么区别,支持 SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY 及 LIMIT 子句。SELECT 子句中可以使用星号或文档字段名称列表,FROM 子句则指定要检索的索引名称,而 WHERE 子句则设定了检索的条件。一般的 SQL 查询使用这三个子句就足够了,而 GROUP BY 和 HAVING 子句则用于分组,ORDER BY 子句用于排序,而 LIMIT 一般则可以用于分页。由于与传统 SQL 语句非常接近,这里就不再对它们的详细使用方法做更进一步介绍了。

ℹ️ DESCRIBE 语句

DESCRIBE 语句用于查看一个索引的基础信息,在返回结果中一般会包含 column、type、mapping 三个列,分别对应文档的字段名称、传统数据库类型及文档字段中的类型。例如要查看索引的基本信息,可以按如下发送 DESCRIBE 语句:

复制代码
POST _sql?format=json
{
    "query": "describe students"
}

ℹ️ SHOW 语句

SHOW 语句包括三种形式,即 SHOW COLUMNS、SHOW FUNCTIONS 和 SHOW TABLES。SHOW COLUMNS 用于查看一个索引中的字段情况,它的作用与 DESCRIBE 语句完全一样,甚至连返回结果都是一样的。SHOW FUNCTIONS 用于返回在 Elasticsearch SQL 中支持的所有函数,返回结果中包括 MIN、MAX、COUNT 等常用的聚集函数。最后,SHOW TABLES 用于查看 Elasticsearch 中所有的索引。如下展示在 _sql 接口中如何使用这三种形式:

复制代码
POST _sql?format=json
{
    "query": "show columns in students"
}

POST _sql?format=json
{
    "query": "show functions"
}

POST _sql?format=json
{
    "query": "show tables"
}

这三种形式都支持使用 LIKE 子句过滤返回结果,LIKE 子句在用法上与 SQL 语句中的 LIKE 类似。例如,"show functions like 'a%'" 将只返回以 a 开头的函数。

7.3 操作符与函数

我们来看一下比较操作符。一般等于比较在 SQL 中使用等号=,这在 Elasticsearch SQL 中也成立。但是 Elasticsearch SQL 还引入了另一个等号比较<=>,这种等号可以在左值为 null 时不出现异常。如下:

复制代码
select null = 'elastic'        # 返回 null
select null <=> 'elastic'      # 返回 false

再来看一下 LIKE 操作符。在 LIKE 子句中可以使用%代表任意多个字符,而使用_代表单个字符。Elasticsearch SQL 不仅支持 LIKE 子句,还支持通过 RLIKE 子句以正则表达式的形式做匹配,这大大扩展了 SQL 语句模糊匹配的能力。

尽管使用 LIKE 和 RLIKE 可以实现模糊匹配,但它离全文检索还差得很远。SQL 语句的 WHERE 子句一般都是使用字段整体值做比较,而没有使用词项做匹配的能力。为此 Elasticsearch SQL 提供了 MATCH 和 QUERY 两个函数,以实现在 SQL 做全文检索。前者最终会翻译为 DSL 中的match或multi_match查询,而后者则为query_string。在如下示例中的两个请求分别使用 match 和 query 函数,它们的作用都是检索 DestCountry 字段为 CN 的文档:

复制代码
POST _sql?format=json
{
    "query": """
        select id, title, description, score()
        from students
        where match(title, 'Rocky')
    """
}
POST _sql?format=json
{
    "query": """
        select id, title, description, score()
        from students
        where query('title:Rocky')
    """
}

在以上示例中的两个请求的 select 子句中都使用了 SCORE 函数,它的作用是获取检索的相关度评分值。

Elasticsearch SQL 支持传统 SQL 中的聚集函数,这包括 MAX、MIN、AVG、COUNT、SUM等。同时,它还支持一些 Elasticsearch 特有的聚集函数,这些聚集函数与 Elasticsearch 聚集查询相对应。这包括 FIRST/FIRST_VALUE 和 LAST/LAST_VALUE,可用于查看某个字段首个和最后一个非空值;PERCENTILE 和 PERCENTILE_RANK 用于百分位聚集,KURTOSIS、SKEWNESS、STDDEV_POP、SUM_OF_SQUARES 和 VAR_POP 可用于运算其他统计聚集。

八、结语

很吃力,希望我能看的懂。

我也是为了总结,为了复习,才有这样的文章。有不妥,要调整。有遐思,要调顺。

写的不好🤣

相关推荐
尽兴-6 个月前
Elasticsearch 入门全景:核心概念与典型应用场景速览
大数据·elasticsearch·搜索引擎·全文检索·高性能·elastic stack·分布式生态
Wang's Blog10 个月前
Elastic Stack梳理:核心机制深度解析之倒排索引、实时性与数据持久化优化
elastic stack
程序员 jet_qi3 年前
ElasticSearch第三讲:ES详解 - Elastic Stack生态和场景方案
大数据·elasticsearch·kibana·logstash·elastic stack·日志收集·beats