数据类型
核心数据类型
-
字符串类型: text, keyword
-
数字类型:long, integer, short, byte, double, float, half_float, scaled_float
-
日期:date
-
日期 纳秒:date_nanos
-
布尔型:boolean
-
二进制:binary
-
范围类型: integer_range, float_range, long_range, double_range, date_range
复杂数据类型
数组类型:array
对象类型:object
嵌套类型:nested object(用于json对象数组)
地理位置数据类型
geo_point(点)、geo_shape(形状)
专用类型
- ip类型:记录IP地址ip
- 补全类型:实现自动补全completion
- 记录分词数:token_count,用于统计字符串种的词条数量
- 记录字符串hash值母乳murmur3
多字段特性multi-fields
允许对同一个字段采用不同的配置,比如分词,例如对人名实现拼音搜索,只需要在人名中新增一个子字段为pinyin即可
es节点信息及索引查询
# 查看所有节点
GET /_cat/nodes
# 查看es健康状况
GET /_cat/health
# 查看主节点
GET /_cat/master
# 查看所有索引
# 相当于mysql的show databases
GET /_cat/indices
类型type的操作(7版本之后已弃用)
文档操作
基础crud
// 根据id查询文档 索引/类型/id
// 这个只能根据id查,必须携带id
// 这个语句只能查真实的索引,不能查聚合的索引,
// 例如只能查索引index_2022.01.01,不能查index*
GET localhost:9200/blog1/article/1
DELETE localhost:9200/blog1/article/1
POST localhost:9200/blog1/article/1
// article为表名,1为文档的id(就是es自带的id,不是我们的业务id),如果不指定,es会自动分配一个string类型的id
// 修改文档和创建文档的格式一样,id重复就是更新
{
"id":1,
"title":"亮剑野狼峪白刃战",
}
// 这也是更新,与不带_update更新不同的是
// 上面更新完之后再更新同样的内容_seq_no,_version会累加
// 这儿更新完之后再更新同样的内容_seq_no,_version不会累加,result返回noop(no operation)
// post和put一样
// 也可以增加属性,直接写就行,上面的也可以,post和put也都可以
POST /customer/external/1/_update
{
"doc":{
"name":"jay"
}
}
// _bulk:批量写入数据
// 这的json不能换行,否则会报错
// 第一行是用来定义规则的
// index:创建或更新,create:创建,update:更新,delete:删除
// 上一条如果失败不会影响到下一条
POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"author":"gaosilin","price":1000,"id":2,"birth":"2022-03-28T06:59:59Z"}
{"index":{"_id":"2"}}
{"author":"gaosiling","price":1000,"id":3}
// 下面执行的话,是需要合并到一起的
// 这里分开为了好看
// _retry_on_conflict:3 更新失败重试3次
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog first"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my first blog first"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my first blog first"}}
查询结果说明
// _version不是1,证明数据被更新过
// _seq_no和_primary_term做乐观锁操作
// 只要数据有改动它俩都会累加
// _seq_no 并发控制字段,每次更新就会+1,用来做乐观锁
// _primary_term 同上,主分片重新分配,如重启,就会变化
// found 数据被找到了
// 乐观锁
// 例如a,b并发都想修改1数据,a想把1改成2,b想把1改成3,
// 两者都只想改1,而不是改其改2或者3
// 这个时候就可以用_seq_no来控制,老版的是用_version控制
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 4,
"_seq_no" : 3,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "john doe"
}
}
_seq_no和_primary_term实现乐观锁
// 两个只能有一个被改,如果想再改,需要if_seq_no+1
// if_seq_no if_primary_term 是文档级的还是类型及的还是索引级的暂时不关心,以后应用到再说
PUT /customer/external/1?if_seq_no=9&if_primary_term=2
{"name":"1"}
PUT /customer/external/1?if_seq_no=9&if_primary_term=2
{"name":"2"}
es测试数据
POST /bank/account/_bulk
https://gitee.com/xlh_blog/common_content/blob/master/es%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE.json#
文档进阶操作
检索
// q=* 表示查询所有 默认只返回10条记录
GET bank/_search?q=*&sort=account_number:asc
// query dsl
// 我们以后都用这种方式
GET bank/_search
{
"query":{
"match_all": {}
},
"sort": [
{
"account_number": "asc"
},
{
"balance": "desc"
}
]
}
// 排序也可以这么写,上面的是简写
// 分页查询所有,并返回指定的字段
GET bank/_search
{
"query":{
"match_all": {}
},
"sort": [
{
"balance": {
"order": "desc"
}
}
],
"from": 0,
"size": 20,
"_source": ["balance","firstname"]
}
// 数值是精确匹配
GET bank/_search
{
"query": {
"match": {
"balance": "49989"
}
}
}
// 这是模糊查询,也叫全文检索(按照评分score进行排序)
// Mill road 会将这个分词,最终Mill 和 road都会被查出来
// 如果查询不想被分词,则把match替换为match_phrase
GET bank/_search
{
"query": {
"match": {
"address": "Mill road"
}
}
}
// 多字段匹配
// 也会进行匹配,mill和movico在address或者city里都可以被查出来
GET bank/_search
{
"query": {
"multi_match": {
"query": "mill movico",
"fields": ["address","city"]
}
}
}
// bool 复合查询,多条件查询
// should 字段最好属于值,不属于也可以,
// 主要的作用是会影响score,进而影响展示的先后顺序
// must must_not should 都会影响score
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "mill"
}
}
],
"must_not": [
{
"match": {
"age": "28"
}
}
],
"should": [
{
"match": {
"lastname": "Hines"
}
}
]
}
}
}
// filter对score没有影响
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
},
{
"match": {
"address": "mill road"
}
}
],
"filter": {
"range": {
"age": {
"gte": 10,
"lte": 29
}
}
}
}
}
}
// term 一般只针对具体的值进行查询,
// 我们规定,全文检索用 match,非text字段我们用term
// 第二个查询语句是不会返回任何值的,即使其中有一个数据的值就是 451 Humboldt Street
GET bank/_search
{
"query": {
"term": {
"age": {
"value": "28"
}
}
}
}
GET bank/_search
{
"query": {
"term": {
"address": {
"value": "451 Humboldt Street"
}
}
}
}
// match_phrase 与 字段.keyword 区别
// match_phrase 做词语匹配,只要值包含即可
// .keyword 做精确匹配,必须是这个值
GET bank/_search
{
"query": {
"match": {
"address.keyword": "789 Madison"
}
}
}
GET bank/_search
{
"query": {
"match_phrase": {
"address": "789 Madison"
}
}
}
// 使用query_string多字段查询
POST localhost:9200/blog1/article/_search
{
"query":{
"query_string":{
"fields":["title","content"],
"query":"我是程序员"
}
},
"size":50,
"from":0
}
查询条件的关键字
-
match
:模糊匹配,需要指定字段名,但是输入会进行分词,比如"hello world"会进行拆分为hello和world,然后匹配,如果字段中包含hello或者world,或者都包含的结果都会被查询出来,也就是说match是一个部分匹配的模糊查询。查询条件相对来说比较宽松。 -
term
: 这种查询和match在有些时候是等价的,比如我们查询单个的词hello,那么会和match查询结果一样,但是如果查询"hello world",结果就相差很大,因为这个输入不会进行分词,就是说查询的时候,是查询字段分词结果中是否有"hello world"的字样,而不是查询字段中包含"hello world"的字样,elasticsearch会对字段内容进行分词,“hello world"会被分成hello和world,不存在"hello world”,因此这里的查询结果会为空。这也是term查询和match的区别
。 -
match_phase
:会对输入做分词,但是需要结果中也包含所有的分词,而且顺序要求一样。以"hello world"为例,要求结果中必须包含hello和world,而且还要求他们是连着的,顺序也是固定的,hello that word不满足,world hello也不满足条件。 -
query_string
:和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。 -
其他
- wildcard:通配符(一般不用,会影响效率),例如 苏*
- prefix:前缀(一般不用,会影响效率)
- fuzzy:模糊
- range:范围(例如查6-10)
- text:
- missing:
聚合分析 aggregations
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 SQL 的 GROUP BY 和聚合函数。
# 搜索address中包含mill的所有人的年龄分布以及平均年龄
# aggs,值被query出来后,进行聚合
# ageAgg 给聚合起个名字
# terms 聚合类型,看值有多少种可能
# size,如果age有100种可能,我们只取10个
# 返回的结果,key age的具体的值,doc_count 该值一共有多少条
# avg 求平均值
# size 0 表示不展示搜索的结果
GET bank/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
},
"balanceAvg":{
"avg": {
"field": "balance"
}
}
},
"size": 0
}
# 子聚合
# 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg":{
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
# 查出所有年龄分布,并且这些年龄段种M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword",
"size": 100
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"balanceAvg":{
"avg": {
"field": "balance"
}
}
}
}
}
}
映射
# 查询索引映射信息
GET bank/_mapping
# 创建索引并指定映射
PUT /my_index
{
"mappings": {
"properties": {
"age":{
"type": "integer"
},
"email":{
"type": "keyword"
},
"name":{
"type": "text"
}
}
}
}
# 添加映射,只能添加新的字段,不能更新已有字段的映射
# index 是否可以用来检索
# store 是否存储
# analyzer 分词器
# doc_values 是否可以被聚合
PUT /my_index
{
"mappings": {
"properties": {
"employee-id":{
"type": "keyword",
"index": true
}
}
}
}
GET bank/_search
GET bank/_mapping
GET new_bank/_mapping
# 更新映射
# 对于已存在的字段,字段的类型是不能被更新的
# 我们需要创建一个新的索引进行数据迁移
PUT /new_bank
{
"mappings": {
"properties": {
"account_number" : {
"type" : "long"
},
"address" : {
"type" : "text"
},
"age" : {
"type" : "integer"
},
"balance" : {
"type" : "long"
},
"city" : {
"type" : "keyword"
},
"email" : {
"type" : "keyword"
},
"employer" : {
"type" : "keyword"
},
"firstname" : {
"type" : "text"
},
"gender" : {
"type" : "keyword"
},
"lastname" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"state" : {
"type" : "keyword"
}
}
}
}
# 数据迁移
# 可以不指定类型,数据是老版本的数据
# 在7以后的版本中就可以不指定type了
POST _reindex
{
"source": {
"index": "bank",
"type": "account"
},
"dest": {
"index": "new_bank"
}
}
GET new_bank/_search
nested
PUT my_index/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
GET my_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"user.first": "Alice"
}
},
{
"match": {
"user.last": "Smith"
}
}
]
}
}
}
上面的查询两个条件都是必须
,理论上来讲没有一个user是Smith Alice
的,但实际情况我们是可以查出来的
这是因为es把first和last的所有值都分别存在了一个数组里,查询的时候es发现这两个值在其数组中都存在,就有了结果
// 指定user为nested类型就不会出现这种情况了
PUT my_index
{
"mappings": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
特殊查询
查询每天7:50的数据
实际查的是15:50的数据,这里是因为es时区的关系
POST your_es_index*/_search
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"source": "doc['@timestamp'].value.getHour() == params.queryHour && doc['@timestamp'].value.getMinute() == params.queryMinute ",
"params": {
"queryHour":7,
"queryMinute": 50
}
}
}
},
"must": [
{
"term": {
"license.keyword":"巴啦啦"
}
},
{
"range": {
"@timestamp": {
"gte": "2023-01-16T15:50:59.999+0800",
"lte": "2024-01-17T15:50:00.000+0800"
}
}
}
]
}
},
"sort": [
{
"@timestamp": {
"order": "desc"
}
}
],
"from": 0,
"size": 366
}
部分知识引用自:
https://www.cnblogs.com/betterwgo/p/11571275.html
https://www.jianshu.com/p/7247ac3663f7