Elasticsearch提供了基于JSON的完整查询DSL(特定于域的语言)来定义查询。将查询DSL视为查询的AST(抽象语法树),它由两种子句组成:
- Leaf query Cluase 叶子查询(简单查询):这种查询可以单独使用,针对指定的字段查询指定的值。
- Compound query clauses 复杂查询:复杂查询可以包含叶子或者其它的复杂查询语句,用于组合成复杂的查询语句,比如not, bool等。
查询虽然包含这两种,但是查询的行为还与查询的执行环境有关,不同的执行环境,查询操作也不一样。
查询的行为取决于他们所在的查询上下文,包括Query查询上下文和Filter查询上下文。
我们在使用ElasticSearch的时候,避免不了使用DSL语句去查询,就像使用关系型数据库的时候要学会SQL语法一样。如果我们学习好了DSL语法的使用,那么在日后使用和使用Java Client调用时候也会变得非常简单。
本人在自己的电脑搭建了单机的ES:http://127.0.0.1:9200/,版本是ES6.1.1。
查看所有索引
GET http://127.0.0.1:9200/_cat/indices?v
创建索引
PUT http://127.0.0.1:9200/commodity?pretty
其中commodity为索引名称,默认情况下,创建的索引分片数量是 5 个,副本数量是 1 个。
在任意的查询字符串中增加pretty参数,会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。
可以在创建索引的时候通过如下参数来指定分片数、副本数量:
{
"settings":{
"number_of_shards":3,
"number_of_replicas":2
}
}
查看索引的字段类型(mapping)
GET http://127.0.0.1:9200/commodity?_mapping
创建映射(mapping)
PUT http://127.0.0.1:9200/commodity/userinfo/_mapping
其中userinfo相当于关联数据库中的表名, mapping就是字段类型
请求参数如下:
{
"properties":{
"name":{
"type":"text",
"store":false
},
"city":{
"type":"text",
"store":false
},
"age":{
"type":"long",
"store":false
},
"description":{
"type":"text",
"store":false
}
}
}
删除索引
DELETE http://127.0.0.1:9200/commodity
新增文档数据
PUT http://127.0.0.1:9200/commodity/userinfo/1
新增id=1的数据:
{
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来自湖北武汉!"
}
批量插入数据:
创建一个文件insertdb.txt,内容为:
{"index":{ "_index": "commodity", "_type": "userinfo", "_id": "3" }}
{"name":"赵括","age":55,"city":"赵国","description":"赵括来自古代赵国!"}
{"index":{ "_index": "commodity", "_type": "userinfo", "_id": "4" }}
{"name":"秦始皇","age":22,"city":"秦国","description":"秦始皇来自古代秦国!"}
切记每一条记录后都需要回车添加换行符,以文件的形式执行以下命令。
POST http://127.0.0.1:9200/_bulk
更新文档数据
(1) 覆盖原来数据
PUT http://127.0.0.1:9200/commodity/userinfo/1
{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
(2) 不会覆盖原来数据,只会更新某个域的数据
POST http://127.0.0.1:9200/commodity/userinfo/1/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
}
删除文档数据
(1) 根据ID删除数据
DELETE http://127.0.0.1:9200/commodity/userinfo/1
查询数据
(1) 根据id查询数据
GET http://127.0.0.1:9200/commodity/userinfo/1
(2) 查询某个类型(表)的所有数据
GET http://127.0.0.1:9200/commodity/userinfo/_search
参数为:
{
"query": {
"match_all": {}
}
}
query为查询关键字。
match_all 匹配所有的文档,也可以写match_none不匹配任何文档。
返回结果为:
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.0,
"hits": [{
"_index": "commodity",
"_type": "userinfo",
"_id": "1",
"_score": 1.0,
"_source": {
"name": "张三丰",
"age": 22,
"city": "深圳",
"description": "在武汉读书,家在武汉!在深圳工作!"
}
}]
}
}
- took:表示执行整个搜索请求消耗了多少毫秒
- timed_out:表示本次查询是否超时。当timed_out为True时也会返回结果,这个结果是在请求超时时ES已经获取到的数据,所以返回的这个数据可能不完整。且当你收到timed_out为True之后,虽然这个连接已经关闭,但在后台这个查询并没有结束,而是会继续执行。
- _shards: 显示查询中参与的分片信息,成功多少分片失败多少分片等
- hits: 匹配到的文档的信息,其中total表示匹配到的文档总数,max_score为文档中所有_score的最大值。hits中的hits数组为查询到的文档结果,默认包含查询结果的前十个文档,每个文档都包含文档的_index、_type、_id、_score和_source数据。
结果文档默认情况下是按照相关度(_score)进行降序排列,也就是说最先返回的是相关度最高的文档,文档相关度意思是文档内容与查询条件的匹配程度。
(3) 返回指定数量的数据
GET http://127.0.0.1:9200/commodity/userinfo/_search
参数如下:
{
"size": 1,
"query": {
"match_all": {}
}
}
(4) 分页查询
GET http://127.0.0.1:9200/commodity/userinfo/_search
参数如下:
{
"from": 0,
"size": 2,
"query": {
"match_all": {}
}
}
size: 设置一次返回的结果数量,也就是hits中的文档数量,默认为10。
from: 设置从第几个结果开始往后查询,默认值为0。
(5) 根据指定字段排序
GET http://127.0.0.1:9200/commodity/userinfo/_search
按照年龄age降序查询:
{
"query": {
"match_all": {}
},
"sort": {
"age": {
"order": "desc"
}
}
}
返回结果:
{
"took": 31,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": null,
"hits": [{
"_index": "commodity",
"_type": "userinfo",
"_id": "2",
"_score": null,
"_source": {
"name": "王五",
"age": 31,
"city": "广州 ",
"description": "王五来自广东广州! "
},
"sort": [31]
}, {
"_index": "commodity",
"_type": "userinfo",
"_id": "1",
"_score": null,
"_source": {
"name": "张三丰",
"age": 22,
"city": "深圳",
"description": "在武汉读书,家在武汉!在深圳工作!"
},
"sort": [22]
}]
}
}
(6) 全文查询
上边有用到一个match_all的全文查询关键字,match_all为查询所有记录,常用的查询关键字在ES中还有以下几个。
① match
最简单的查询,会先对搜索词进行分词,比如“白雪公主和苹果”,会分成“白雪”“公主”“苹果”。含有相关内容的字段,都会被检索出来,相当于我们平时所说的模糊查询。
以下查询age=22的记录:
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"match": {
"age": 22
}
}
}
② multi_match
在多个字段上执行相同的match查询,下边的例子就表示查询city或description字段中包含深圳的记录。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"multi_match": {
"query": "深圳",
"fields": ["city", "description"]
}
}
}
③ query_string
可以在查询里边使用AND或者OR来完成复杂的查询,例如:
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"query_string": {
"query": "(深圳) OR (广州)",
"fields": ["city"]
}
}
}
以上表示查找city为广州或者深圳的所有记录。
也可以用下边这种方式组合更多的条件完成更复杂的查询请求。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"query_string": {
"query": "city:深圳 OR (city:广州 AND age:22)"
}
}
}
以上表示查询(city为深圳)或者是(city为广州且age为22)的所有记录
与其像类似的还有个simple_query_string的关键字,可以将query_string中的AND或OR用+或|这样的符号替换掉。
④ term
term可以用来精确匹配,精确匹配的值可以是数字、时间、布尔值或者是设置了not_analyzed不分词的字符串。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"term": {
"age": {
"value": 22
}
}
}
}
term对输入的文本不进行分析,直接精确匹配输出结果,如果要同时匹配多个值可以使用terms。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"terms": {
"age": [22, 31]
}
}
}
⑤ range
range用来查询落在指定区间内的数字或者时间。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"range": {
"age": {
"gte": 15,
"lte": 33
}
}
}
}
以上表示搜索所有年龄为15到33之间的数据,这里的操作符主要有四个gt大于,gte大于等于,lt小于,lte小于等于。
当使用日期作为范围查询时,我们需要注意下日期的格式,官方支持的日期格式主要有两种:
- 时间戳,注意是毫秒粒度
{
"query": {
"range": {
"@timestamp": {
"gte": 1557676800000,
"lte": 1557680400000,
"format":"epoch_millis"
}
}
}
}
- 日期字符串
{
"query": {
"range":{
"@timestamp":{
"gte": "2019-05-13 18:30:00",
"lte": "2019-05-14",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd",
"time_zone": "+08:00"
}
}
}
}
通常更推荐用这种日期字符串的方式,看起来比较清晰,日期格式可以按照自己的习惯输入,只需要format字段指定匹配的格式,如果格式有多个就用||分开,像例子中那样,不过我更推荐用同样的日期格式。
如果日期中缺少年月日这些内容,那么缺少的部分会用unix的开始时间(即1970年1月1日)填充,当你将"format":"dd"指定为格式时,那么"gte":10将被转换成1970-01-10T00:00:00.000Z。
elasticsearch中默认使用的是UTC时间,所以我们在使用时要通过time_zone来设置好时区,以免出错。
⑥ 前缀匹配
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"prefix": {
"name": {
"value": "王"
}
}
}
}
(7) 组合查询
通常我们可能需要将很多个条件组合在一起查出最后的结果,这个时候就需要使用ES提供的bool来实现了。
例如我们要查询city为广州且hdescription为广州且age不为22的所有数据就可以使用下边的语句。
GET http://127.0.0.1:9200/commodity/userinfo/_search
{
"query": {
"bool": {
"filter": [{
"match": {
"city": "广州"
}
},
{
"match": {
"description": "广州"
}
}
],
"must_not": {
"match": {
"age": 22
}
}
}
}
}
主要有四个关键字来组合查询之间的关系,分别为:
- must: 类似于SQL中的AND,必须包含。
- must_not: 类似于SQL中的NOT,必须不包含。
- should: 满足这些条件中的任何条件都会增加评分_score,不满足也不影响,should只会影响查询结果的_score值,并不会影响结果的内容。
- filter: 与must相似,但不会对结果进行相关性评分_score,大多数情况下我们对于日志的需求都无相关性的要求,所以建议查询的过程中多用filter。
聚合统计
Es相比关系型数据库在数据检索方面有着极大的优势,在处理亿级数据时,可谓是毫秒级响应,我们在使用Es时不仅仅进行简单的查询,有时候会做一些数据统计与分析,如果你以前是使用的关系型数据库,那么Es的数据统计跟关系型数据库还是有很大的区别的。
首先,先了解一下Es中关于聚合的概念:
- 桶(Buckets):满足特定条件的文档的集合;
- 指标(Metrics)对桶内的文档进行统计计算
这两个概念是什么意思?先看下面一段SQL统计代码:
SELECT Color,SUM(1) as Nums【2】
FROM #Cars
GROUP BY Color 【1】
桶:满足特定条件的集合,这个很好理解,比如可以把蓝色的放到蓝色的桶里,绿色的放到绿色的桶里,桶是用来存放不同类型的集合。SQL代码中【1】就可以理解对桶进行分组,有多少种颜色,就会有几种不同的桶。桶类似于SQL中GROUP BY;
指标:对桶内的数据进行统计计算。SQL代码中【2】就可以理解为指标,每个桶里有多少条记录。指标类似于SQL中各种汇总,如Count(),Sum(),Max(),Min();
接下来,我们就来看看ES的聚合统计和SQL聚合统计的区别,以统计哪个颜色的销量最好为例。
① SQL实现
SELECT Color,SUM(1) as SalesNum
FROM #Cars
GROUP BY Color
② ES实现
GET testindex/cars/_search
{
"size": 0, 【3】
"aggs": {【1】
"SalesNum": { 【2】
"terms": {【4】
"field": "color",
"size": 10
}
}
}
}
【1】:如果想要进行统计分析,统计代码需要写在aggs中,aggs是aggregations 的简称,也可以写作 aggregations。
【2】:是指定的列的名称,作用同SQL统计中as 重命名。
【3】:这里设置了返回值为0,因为这个查询不仅仅返回了我们的统计的内容,还返回了搜索结果的内容,这里我们并不需要搜索结果的内容,所以设置为0。
【4】:这里定义了桶的类型,如果需要不同的统计内容,这些需要使用不同的统计类型。
SQL中汇总函数与ES的对比:
SQ函数 | Agg_Type | 功能说明 |
GROUP BY 字段名称 | terms (避免使用分词字段用来分组) | 分组、Es划分桶 |
max()函数 | max | 求最大值 |
min()函数 | min | 求最小值 |
avg()函数 | avg | 求平均值 |