目录
前言
Elasticsearch可以作为数据库,但它最常用的也是最擅长的还是搜索引擎。本篇介绍elasticsearch搜索数据的DSL(Domain Specific Language)语法。部分内容总结摘抄自《Elasticsearch实战》,仅作笔记。另外,本篇文章基于es7.x,因此类型统一为_doc。
一、搜索前的准备
为了方便后面执行DSL语句以及可视化索引信息,我们使用kibana和head工具。首先使用kibana执行下面的语句创建一个主分片数量为1且副本分片数量为0的索引。
PUT /index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
},
"gender": {
"type": "keyword"
},
"birthday":{
"type": "date",
"format": "yyyy-MM-dd"
},
"personalIntro":{
"type": "text"
}
}
}
}
执行完之后在head插件中就可以新增了一个名为index的索引,点击查看索引信息如下,可以看到与我们创建的映射一致。
然后向该索引中添加5条数据。
POST /index/_doc/
{
"name":"charles hahaha",
"age":18,
"gender":"male",
"birthday":"1991-10-22",
"personalIntro":"I am a handsome boy."
}
POST /index/_doc/
{
"name":"charles kakakakak",
"age":8,
"gender":"male",
"birthday":"1991-10-22",
"personalIntro":"Nice to meet you!"
}
POST /index/_doc/
{
"name":"krystal hahahahahhahahh",
"age":28,
"gender":"female",
"birthday":"1994-10-22",
"personalIntro":"I am a handsome girl,my name is charles hahahahahahaaaaaa."
}
POST /index/_doc/
{
"name":"charles wawawawawa",
"age":18,
"gender":"female",
"birthday":"1991-10-22",
"personalIntro":"I am a beautiful girl,my name is krystal hahahahahaha"
}
POST /index/_doc/
{
"name":"krystal wawawawawa",
"age":48,
"gender":"female",
"birthday":"1981-10-22",
"personalIntro":"I am a beautiful girl,what is your name?"
}
然后在head插件中查看数据已经插入成功,这里把数据放在这里以便后面验证查询结果。
二、搜索结构以及DSL语法
所有的REST搜索请求都使用_search的REST端点,既可以是GET请求也可以是POST请求,既可以搜索整个集群,也可以搜索某个索引。_search端点可以执行DSL语句进行搜索操作,例如:
- GET _search:表示搜索整个集群的索引;
- GET /index/_search:表示搜索索引名称为index的全部数据;
- GET /index-*/search:表示搜索索引名称以index-开头的全部数据。
使用GET /index/_search查询刚刚添加到index索引的数据,返回的结果以及对应的字段表示的意义如下(共5条数据,此处仅显示1条):
{
"took" : 0, ========>表示查询所用的毫秒数
"timed_out" : false, ========>表示是否有分片查询超时,如果有则表示只返回了部分结果
"_shards" : { ========>表示成功响应该请求和未成功响应该请求的分片数量
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : { ========>包含了命中结果的信息
"total" : { ========>表示命中文档的数量
"value" : 5,
"relation" : "eq"
},
"max_score" : 1.0, ========>表示此次搜索结果的最大得分,后面的文章会详细介绍
"hits" : [ ========>结果文档数组,包含全部的结果文档
{
"_index" : "index", ========>结果文档的索引名称
"_type" : "_doc", ========>结果文档的类型
"_id" : "QL-pAHsBwtANfSOOuSd8", ========>结果文档的id
"_score" : 1.0, ========>结果文档的相关性得分
"_source" : { ========>真实存储文档的数据
"name" : "charles hahaha",
"age" : 18,
"gender" : "male",
"birthday" : "1991-10-22",
"personalIntro" : "I am a handsome boy."
}
}
]
}
}
1、搜索请求的基本模块
为了应对各种各样的场景和搜索条件,我们需要配置一些必要的模块和一些定制化的条件。基本模块包括以下几个:
- query:这是搜索请求中最重要的部分,通过它可以配置搜索条件、过滤器DSL以及查询DSL等等;
- size:表示返回结果的最大数量;
- from:表示从第几个文档开始,和size一起使用,可以用于分页操作;
- _source:_source存储了整个文档的全部数据,默认是将_source的全部数据返回,通过配置_source可以指定返回某些字段;
- sort:默认的排序是文档的得分,配置sort可以指定返回文档的排序,可以指定一个或多个字段升序(asc)或降序(desc)排列;
例如我们想要返回以age倒序排列的第三条数据和第四条数据,返回结果只包含name和age这两个字段,查询语句如下(关于match_all在后面会详细介绍):
GET /index/_search
{
"query":{
"match_all":{}
},
"size":2,
"from":2,
"sort":{
"age":"desc"
},
"_source":[
"name","age"
]
}
查询结果如下,按照age倒序排列的话正好是age为18的那两条数据,并且_source只返回了age和name字段,查询结果符合预期。
2、简单查询DSL
查询表达式(query DSL)是一种非常灵活而又富有表现力的查询语言,Elasticsearch使用它可以以简单的JSON接口来展现Lucene功能的绝大部分。Elasticsearch的查询语言拥有一套查询组件,这些组件可以以无限组合的方式进行搭配,可以在两种情况下使用:分别是查询和过滤器,它们的区别可以简单概况如下:
- 查询会为特定的词条计算得分,而过滤器只会关注文档是否匹配了当前查询;
- 由于第1条,过滤器比查询快,且可以缓存;
- 过滤器有自己额外的映射,在查询的DSL中这种查询被称为过滤查询,过滤查询包括两个模块:查询和过滤器,其中查询与普通查询相同,过滤器则是指定过滤器和限制条件。过滤查询的结构如下(事实上,filtered在5.x版本也弃用了O__O "…,被bool查询替换):
{
"query":{
"filtered":{
"query":{
查询
},
"filter":{
过滤器
}
}
}
上一小节提到的match_all就是一个查询,或者说一个查询方式。接下来介绍常用的查询和过滤器以及它们的用法和DSL语法。
1、match_all查询
match_all查询会匹配所有的文档,当未指明查询方式时默认就是match_all。在查询语句中使用过滤器而不是查询的时候或者希望返回被搜索的索引包含的全部文档的时候可以使用match_all查询。
当查询索引中全部文档时的语法如下:
GET /index/_search
{
"query": {
"match_all":{}
}
}
当在搜索中使用过滤器时的语法如下:
GET /index/_search
{
"query": {
"filtered":{
"query":{
"match_all":{}
},
"filter":{
过滤器
}
}
}
}
2、match查询
match查询可以通过给定的关键词模糊搜索指定文档中的某个字段,例如想要查询index索引中name字段包含“charles”的数据,可以使用如下查询语句:
GET /index/_search
{
"query": {
"match": {
"name": "charles"
}
}
}
查询结果如下,可以看到查询到的都是name包含了charles的数据。
以上只是match的最简单的用法,match查询可以有多种行为方式,其中最常见的是布尔和词组。
布尔查询
match查询的字段的值如果以空格分割,match会使用布尔行为和or操作符。例如我们来查询index索引中name字段包含charles或krystal的数据,语句如下:
GET /index/_search
{
"query":{
"match": {
"name": {
"query": "charles krystal "
}
}
}
}
查询结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : 0.87546873,
"hits" : [
{
"_index" : "index",
"_type" : "_doc",
"_id" : "TL8AAXsBwtANfSOOaycV",
"_score" : 0.87546873,
"_source" : {
"name" : "krystal hahahahahhahahh",
"age" : 28,
"gender" : "female",
"birthday" : "1994-10-22",
"personalIntro" : "I am a handsome girl,my name is charles hahahahahahaaaaaa."
}
},
{
"_index" : "index",
"_type" : "_doc",
"_id" : "Tr8AAXsBwtANfSOOdyfo",
"_score" : 0.87546873,
"_source" : {
"name" : "krystal wawawawawa",
"age" : 48,
"gender" : "female",
"birthday" : "1981-10-22",
"personalIntro" : "I am a beautiful girl,what is your name?"
}
},
{
"_index" : "index",
"_type" : "_doc",
"_id" : "Sr8AAXsBwtANfSOOVSe4",
"_score" : 0.5389965,
"_source" : {
"name" : "charles hahaha",
"age" : 18,
"gender" : "male",
"birthday" : "1991-10-22",
"personalIntro" : "I am a handsome boy."
}
},
{
"_index" : "index",
"_type" : "_doc",
"_id" : "S78AAXsBwtANfSOOYycQ",
"_score" : 0.5389965,
"_source" : {
"name" : "charles kakakakak",
"age" : 8,
"gender" : "male",
"birthday" : "1991-10-22",
"personalIntro" : "Nice to meet you!"
}
},
{
"_index" : "index",
"_type" : "_doc",
"_id" : "Tb8AAXsBwtANfSOOcCda",
"_score" : 0.5389965,
"_source" : {
"name" : "charles wawawawawa",
"age" : 18,
"gender" : "female",
"birthday" : "1991-10-22",
"personalIntro" : "I am a beautiful girl,my name is krystal hahahahahaha"
}
}
]
}
}
可以看到name中包含charles或krystal的数据都被查询出来了,此处如果想使用and操作符,则需要指定操作符,例如想要查询personalIntro字段中同时包含handsome和charles的数据,语句如下:
GET /index/_search
{
"query":{
"match": {
"personalIntro": {
"query": "charles handsome ",
"operator": "and"
}
}
}
}
查询结果如下,可以看出符合预期查询。
词组查询
match还可以作为phrase查询,在查询中指定一个词组(词之间以空格分隔,包含一个以上的分词,分词顺序必须与数据中的字段值相同才能查询出来),然后指定单词之间允许存在的最大分词个数(slop字段),即可查询执行字段中包含该词组的数据。例如想要查询index索引中personalIntro字段中包含词组“I handsome”且允许存在的最大分词个数为5的数据,语句如下:
GET /index/_search
{
"query":{
"match": {
"personalIntro": {
"query": "charles handsome ",
"slop": 5
}
}
}
}
需要注意的是,在es6中就已经将match查询中的slop字段移除了,笔者的es版本是7.x,因此执行以上语句的结果是:
当然在es6以前的版本上述语法还是可以用的,这里多说一句,es的版本更新非常频繁且每个大版本甚至小版本在包括语法的各方面使用可能都会有不小的改变,因此一定要对应自己使用的版本来学习。