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

求平均值