Elasticsearch学习笔记(一)
目录
一、操作索引
1.1 基本概念
1.2 创建索引
1.3 查询索引
1.4 删除索引
1.5 映射配置
1.5.1 创建映射字段
1.5.2 查看映射关系
1.5.3 字段属性详解
1.6 新增数据
1.6.1 随机生成id
1.6.3 智能判断
1.7 修改数据
1.8 删除数据
二、查询
2.1 基本查询
2.1.1 查询所有
2.1.2 匹配查询
2.1.3 多字段查询
2.1.4 词条匹配
2.1.5 多词条精确匹配
2.2 结果过滤
2.2.1 直接指定字段
2.2.2 指定includes和excludes
2.3 高级查询
2.3.1 布尔组合
2.3.2 范围查询
2.3.4 模糊查询
2.4 过滤
2.4.1 条件查询中进行过滤
2.4.2 无查询条件直接过滤
2.5 排序
2.5.1 单字段排序
2.5.2 多字段排序
三、聚合aggregations
3.1 基本概念
3.2 聚合为桶
3.3 桶内度量
3.4 桶内嵌套桶
3.5 划分桶的其他方式
3.5.1 阶梯分桶Histogram
3.5.2 范围分桶range
lasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。
对比关系:
索引(indices)--------------------------------Databases 数据库
类型(type)-----------------------------Table 数据表
文档(Document)----------------Row 行
字段(Field)-------------------Columns 列
概念 | 说明 |
---|---|
索引库(indices) | indices是index的复数,代表许多的索引, |
类型(type) | 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念 |
文档(document) | 存入索引库原始的数据。比如每一条商品信息,就是一个文档 |
字段(field) | 文档中的属性 |
映射配置(mappings) | 字段的数据类型、属性、是否索引、是否存储等特性 |
注意:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。
Elasticsearch采用Rest风格API,因此其API就是一次http请求,可以用任何工具发起http请求
创建索引的请求格式:
请求方式:PUT
请求路径:/索引库名
请求参数:json格式
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
settings:索引库的设置
number_of_shards:分片数量
number_of_replicas:副本数量
Get请求可以帮我们查看索引信息,格式:
GET /索引库名
删除索引使用DELETE请求
DELETE /索引库名
索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。什么是映射?映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等,只有配置清楚,Elasticsearch才会帮我们进行索引库的创建。
PUT /索引库名/_mapping/类型名称
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
类型名称:类似于数据库中的不同表
字段名:任意填写 ,可以指定许多属性。
type:类型,可以是text、long、short、date、integer、object等
index:是否索引,默认为true
store:是否存储,默认为false
analyzer:分词器,这里的ik_max_word
即使用ik分词器
GET /索引库名/_mapping
type
Elasticsearch中支持的数据类型非常丰富:
String类型,又分两种:
text:可分词,不可参与聚合
keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
Numerical:数值类型,分两类
基本数据类型:long、interger、short、byte、double、float、half_float
浮点数的高精度类型:scaled_float
需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
Date:日期类型
elasticsearch可以对日期格式化为字符串存储,但是建议存储为毫秒值,存储为long,节省空间。
index
index影响字段的索引情况。
true:字段会被索引,则可以用来进行搜索。默认值就是true
false:字段不会被索引,不能用来搜索
index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。
但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。
store
是否将数据进行额外存储。
Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做_source
的属性中。而且我们可以通过过滤_source
来选择哪些要显示,哪些不显示。而如果设置store为true,就会在_source
以外额外存储一份数据,多余,因此一般我们都会将store设置为false,事实上,store的默认值就是false。
POST /索引库名/类型名
{
"key":"value"
}
示例:
查询:
_source
:源文档信息,所有的数据都在里面。
_id
:这条文档的唯一标示,与文档自己的id字段没有关联
1.6.2 自定义id
POST /索引库名/类型/id值
{
...
}
示例:
查询:
Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。
示例:
额外添加了stock库存,和saleable是否上架两个字段 。
查询:
索引库的映射关系:
把新增的请求方式改为PUT,就是修改了。不过修改必须指定id,
id对应文档存在,则修改
id对应文档不存在,则新增
比如,把id为3的数据进行修改:
删除使用DELETE请求,同样,需要根据id进行删除:
查询种类:
基本查询
_source
过滤
结果过滤
高级查询
排序
GET /heima/_search
{
"query":{
"match_all": {}
}
}
query
:代表查询对象
match_all
:代表查询所有
查询结果:
took:查询花费时间,单位是毫秒
time_out:是否超时
_shards:分片信息
hits:搜索结果总览对象
total:搜索到的总条数
max_score:所有结果中文档得分的最高分
hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
_index:索引库
_type:文档类型
_id:文档id
_score:文档得分
_source:文档的源数据
先添加一条数据:
PUT /leyou/goods/3
{
"title":"小米电视4A",
"images":"http://image.leyou.com/12479122.jpg",
"price":3899.00
}
or关系
match
类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系
在上面的结果中,不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是or
的关系。
and关系
某些情况下,我们需要更精确查找,希望这个关系变成and:
GET /leyou/_search
{
"query":{
"match": {
"title": {
"query": "小米电视",
"operator": "and"
}
}
}
}
结果中只有同时包含小米
和电视
的词条才会被搜索到。
match
查询支持 minimum_should_match
最小匹配参数, 可以指定必须匹配的词项数用来表示一个文档是否相关。可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数
,因为无法控制用户搜索时输入的单词数量:
GET /leyou/_search
{
"query":{
"match":{
"title":{
"query":"小米曲面电视",
"minimum_should_match": "75%"
}
}
}
}
本例中,搜索语句可以分为3个词,如果使用and关系,需要同时满足3个词才会被搜索到。这里我们采用最小品牌数:75%,那么也就是说只要匹配到总词条数量的75%即可,这里3*75% 约等于2。所以只要包含2个词条就算满足条件了。
结果:
multi_match
与match
类似,不同的是它可以在多个字段中查询
GET /leyou/_search
{
"query":{
"multi_match": {
"query": "小米",
"fields": [ "title", "subTitle" ]
}
}
}
在title和subTitle中查询小米这个词。
term
查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
terms
查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source
的所有字段都返回。
如果只想获取其中的部分字段,可以添加_source
的过滤
我们也可以通过:
includes:来指定想要显示的字段
excludes:来指定不想要显示的字段
二者都是可选的。
bool
把各种其它查询通过must
(与)、must_not
(非)、should
(或)的方式进行组合
range
查询找出那些落在指定区间内的数字或者时间
range
查询允许以下字符:
操作符 | 说明 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
新增一个商品:
POST /leyou/goods/4
{
"title":"apple手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":6899.00
}
fuzzy
查询是 term
查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:
可以通过fuzziness
来指定允许的编辑距离:
GET /leyou/_search
{
"query": {
"fuzzy": {
"title": {
"value":"appla",
"fuzziness":1
}
}
}
}
所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter
方式:
注意:filter
中还可以再次进行bool
组合条件过滤。
如果一次查询只有过滤,没有查询条件,不希望进行评分,可以使用constant_score
取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
sort
可以按照不同的字段进行排序,并且通过order
指定排序的方式
假定想要结合使用 price和 _score(得分) 进行查询,并且匹配的结果首先按照价格排序,然后按照相关性得分排序:
聚合可以让我们极其方便的实现对数据的统计、分析。例如:
什么品牌的手机最受欢迎?
这些手机的平均价格、最高价格、最低价格?
这些手机每月的销售情况如何?
实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。
Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫桶
,一个叫度量
:
桶(bucket)
桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶
,例如我们根据国籍对人划分,可以得到中国桶
、英国桶
,日本桶
……或者我们按照年龄段对人进行划分:0~10,10~20,20~30,30~40等。
Elasticsearch中提供的划分桶的方式有很多:
Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
Histogram Aggregation:根据数值阶梯分组,与日期类似
Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
……
综上所述,我们会发现bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量
度量(metrics)
分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量
比较常用的一些度量聚合方式:
Avg Aggregation:求平均值
Max Aggregation:求最大值
Min Aggregation:求最小值
Percentiles Aggregation:求百分比
Stats Aggregation:同时返回avg、max、min、sum、count等
Sum Aggregation:求和
Top hits Aggregation:求前几
Value Count Aggregation:求总数
……
为了进行测试,需要导入一些数据。
创建索引
PUT /cars
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"transactions": {
"properties": {
"color": {
"type": "keyword"
},
"make": {
"type": "keyword"
}
}
}
}
}
注意:在ES中,需要进行聚合、排序、过滤的字段其处理方式比较特殊,因此不能被分词。这里我们将color和make这两个文字类型的字段设置为keyword类型,这个类型不会被分词,将来就可以参与聚合
导入数据
POST /cars/transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
首先,按照 汽车的颜色color
来划分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
aggs:声明这是一个聚合查询,是aggregations的缩写
popular_colors:给这次聚合起一个名字,任意。
terms:划分桶的方式,这里是根据词条划分
field:划分桶的字段
结果:
hits:查询结果为空,因为我们设置了size为0
aggregations:聚合的结果
popular_colors:我们定义的聚合名称
buckets:查找到的桶,每个不同的color字段值都会形成一个桶
key:这个桶对应的color字段的值
doc_count:这个桶中的文档数量
通过聚合的结果我们发现,目前红色的小车比较畅销!
前面的例子获取到了每个桶里面的文档数量,这很有用。 但通常,应用中需要提供更复杂的文档度量。 例如,每种颜色汽车的平均价格是多少?
因此,需要告诉Elasticsearch使用哪个字段
,使用何种度量方式
进行运算,这些信息要嵌套在桶
内,度量
的运算会基于桶
内的文档进行
现在,为刚刚的聚合结果添加 求价格平均值的度量:
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
度量
也是一个聚合,度量是在桶内的聚合avg_price:聚合的名称
avg:度量的类型,这里是求平均值
field:度量运算的字段
结果:
可以看到每个桶中都有自己的avg_price
字段,这是度量聚合的结果
刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。
比如:我们想统计每种颜色的汽车中,分别属于哪个制造商,按照make
字段再进行分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
},
"maker":{
"terms":{
"field":"make"
}
}
}
}
}
}
maker:在嵌套的aggs下新添一个桶,叫做maker
terms:桶的划分类型依然是词条
filed:这里根据make字段进行划分
结果:
可以看到,新的聚合maker
被嵌套在原来每一个color
的桶中。
每个颜色下面都根据 make
字段进行了分组
我们能读取到的信息:
红色车共有4辆
红色车的平均售价是 $32,500 美元。
其中3辆是 Honda 本田制造,1辆是 BMW 宝马制造。
划分桶的方式有很多,例如:
Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
Histogram Aggregation:根据数值阶梯分组,与日期类似
Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
介绍一下其他方式:
原理
histogram是把数值类型的字段,按照一定的阶梯大小进行分组。你需要指定一个阶梯值(interval)来划分阶梯大小。
举例:
比如你有价格字段,如果你设定interval的值为200,那么阶梯就会是这样的:
0,200,400,600,...
上面列出的是每个阶梯的key,也是区间的启点。
如果一件商品的价格是450,会落入哪个阶梯区间呢?计算公式如下:
bucket_key = Math.floor((value - offset) / interval) * interval + offset
value:就是当前数据的值,本例中是450
offset:起始偏移量,默认为0
interval:阶梯间隔,比如200
因此你得到的key = Math.floor((450 - 0) / 200) * 200 + 0 = 400
举例
比如,对汽车的价格进行分组,指定间隔interval为5000:
GET /cars/_search
{
"size":0,
"aggs":{
"price":{
"histogram": {
"field": "price",
"interval": 5000
}
}
}
}
结果:
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 8,
"max_score": 0,
"hits": []
},
"aggregations": {
"price": {
"buckets": [
{
"key": 10000,
"doc_count": 2
},
{
"key": 15000,
"doc_count": 1
},
{
"key": 20000,
"doc_count": 2
},
{
"key": 25000,
"doc_count": 1
},
{
"key": 30000,
"doc_count": 1
},
{
"key": 35000,
"doc_count": 0
},
{
"key": 40000,
"doc_count": 0
},
{
"key": 45000,
"doc_count": 0
},
{
"key": 50000,
"doc_count": 0
},
{
"key": 55000,
"doc_count": 0
},
{
"key": 60000,
"doc_count": 0
},
{
"key": 65000,
"doc_count": 0
},
{
"key": 70000,
"doc_count": 0
},
{
"key": 75000,
"doc_count": 0
},
{
"key": 80000,
"doc_count": 1
}
]
}
}
}
会发现,中间有大量的文档数量为0 的桶,看起来很丑。可以增加一个参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤
示例:
用kibana将结果变为柱形图
第一步:
第二步:
第三步:
第四步:
第五步:
第六步:
范围分桶与阶梯分桶类似,也是把数字按照阶段进行分组,只不过range方式需要你自己指定每一组的起始和结束大小。