Elasticsearch 查询

Elasticsearch查询主要有两种形式:URI Search、Request Body Search。

URI Search: 操作简单,方便通过命令行测试,但是仅包含部分查询语法。

GET /my_index/_search?q=user:tom

Request Body Search: es提供的完备查询语法Query DSL。

1
2
3
4
5
6
GET /my_index/_search
{
"query": {
"term": {"user": "tom"}
}
}

URI Search就是通过拼接关键字来进行搜索的,常用的参数如下:

  • q:指定查询语句,语法遵循 Query String Syntax

  • df:q中不指定字段时默认的查询字段,如果不指定,es会查询所有字段

  • sort:排序

  • timeout:指定超时时间,默认不超时

  • from,size:用于分页

如下语句是查询user字段包含tom的文档,结果按照age升序排列,返回5~14个文档,如果超过1s没有结束,则以超时结束:

GET /my_index/_search?q=tom&df=user&sort=age:asc&from=4&size=10&timeout=1s

Query String Syntax

使用下面的命令,创建文档:

1
2
3
4
5
6
7
8
9
POST /test_search_index/doc/_bulk
{"index":{"_id":"1"}}
{"username":"alfred way","job":"java engineer","age":18,"birth":"1990-01-02","isMarried":false}
{"index":{"_id":"2"}}
{"username":"alfred","job":"java senior engineer and java specialist","age":28,"birth":"1980-05-07","isMarried":true}
{"index":{"_id":"3"}}
{"username":"lee","job":"java and ruby engineer","age":22,"birth":"1985-08-07","isMarried":false}
{"index":{"_id":"4"}}
{"username":"alfred junior way","job":"ruby engineer","age":23,"birth":"1989-08-07","isMarried":false}
泛查询

不指定字段查询,等效于在所有字段去匹配该term。

GET /test_search_index/_search?q=alfred

因3个文档的username字段都包含alfred,所以上面的查询将返回3个文档。

如果需要查看查询语句的执行过程(为了方便调优查询语句),可以设置profile为true:

1
2
3
4
GET /test_search_index/_search?q=alfred
{
"profile": "true"
}

服务器将会返回查询语句的执行过程:

"description" : """(MatchNoDocsQuery("failed [isMarried] query, caused by illegal_argument_exception:[Can't parse boolean value [alfred], expected [true] or [false]]") | username.keyword:alfred | MatchNoDocsQuery("failed [birth] query, caused by parse_exception:[failed to parse date field [alfred] with format [strict_date_optional_time||epoch_millis]]") | job:alfred | MatchNoDocsQuery("failed [age] query, caused by number_format_exception:[For input string: "alfred"]") | username:alfred | job.keyword:alfred)"""
指定字段

如下语句指定在username字段查询:

GET /test_search_index/_search?q=username:alfred

返回的结果和上面的泛查询是一样的。

term 与 pharse

如下查询alfred way等效于 alfred OR way:

GET /test_search_index/_search?q=username:alfred way

因查询的是username字段中包含alfred OR way的文档,结果是3个文档都返回。

如下查询加了双引号,表示查询的”alfred way” 词语,要求先后顺序:

GET /test_search_index/_search?q=username:"alfred way"

返回只有id为1的文档。

Group 分组设定

可以使用括号指定匹配的规则:

如下相当于查询username字段中包含alfred OR way的文档:

GET /test_search_index/_search?q=username:(alfred way)

结果是3个文档都会返回。

布尔操作符
AND(&&)、OR(||)、NOT(!)
  • username:(tom NOT lee)

  • 注意大写,不能小写

如下查询的是在username字段中包含alfred,way使用的是泛查询:

GET /test_search_index/_search?q=username:alfred AND way

返回的是2个文档。

如下查询的是在username字段中包含alfred和way的文档:

GET /test_search_index/_search?q=username:(alfred AND way)

返回2个文档,id为2的文档不会返回。

如下查询的是在username字段中包含alfred不包含way的文档:

GET /test_search_index/_search?q=username:(alfred NOT way)

只返回id为2的文档。

must、must_not

+、-分别对应must和must_not

  • username:(tom +lee -alfred)

  • +在url中会被解析为空格,要使用encode后的结果:%2B。

如下是查询username字段中包含alfred但必须包含way的文档:

GET test_search_index/_search?q=username:(alfred %2Bway)

返回的是id为1、4的文档。

范围查询

范围查询支持数值和日期。

区间写法,闭区间用[],开区间用{}

  • age:[1 TO 10] 意为 1<= age <= 10

  • age:[1 TO 10} 意为 1<= age < 10

  • age:[1 TO } 意为 age >= 1

  • age:[* TO 10} 意为 age <= 10

算术符号写法:

  • age:>=1

  • age:(>=1 && <=10)或者age:(+>=1 +<=10)

如下查询的是username字段包含alfred或age大于20的文档:

GET /test_search_index/_search?q=username:alfred age:>20

返回的结果是全部的4个文档。

如下查询的是username字段包含alfred并且age大于20的文档:

GET /test_search_index/_search?q=username:alfred AND age:>20

返回的结果是id为2和4的文档。

如下查询的是birth在1980至1990的文档:

GET /test_search_index/_search?q=birth:(>1980 AND <1990)

返回的结果是id为2、3、4的文档。

通配符查询

?代表1个字符,*代表0或多个字符,如username:t?musername:tom*username:t*m

  • 通配符匹配执行效率低,且占用较多内存,不建议使用。

  • 如无特殊要求,不要将?*放在最前面。

如下查询的是在username字段中以alf开头的所有文档:

GET /test_search_index/_search?q=username:alf*
正则表达式

es的查询也支持正则表达式匹配:username:/[mb]oat/

如下的查询将会返回全部的4个文档:

GET /test_search_index/_search?q=username:/[a]?l.*/
模糊匹配

username:roam~1: 匹配与roam差一个字符的词,比如foam、roams等。

如下查询将会匹配username字段中包含alfred的文档:

GET /test_search_index/_search?q=username:alfed~1
GET /test_search_index/_search?q=username:alfd~2
近似度查询

"fox quick"~5以term为单位进行差异比较,比如"quick fox""quick brown fox"都会被匹配。

如下查询将会返回id为1、2、3的文档:

GET /test_search_index/_search?q=job:"java engineer"~3

Request Body Search 就是将http request body 发送到es,主要参数如下:

  • query:符合 Query DSL语法

  • from,size

  • timeout

  • sort

Query DSL

这是一个基于JSON定义的查询语言,主要包含两种类型:

  • 字段查询: 如 term、match、range等,只针对某一字段进行查询

  • 复合查询: 如bool查询等,包含一个或多个字段类查询或复合查询语句

字段类查询
  • 全文匹配: 针对text类型的字段进行全文检索,会对查询语句先进行分词处理。如match、match_phrase等query类型。

  • 单词匹配: 不会对查询语句做分词处理,直接去匹配字段的倒排索引,如term、terms、range等query类型

match query

如下查询首先会对"alfred way"进行分词,返回的是包含alfred或way的文档:

1
2
3
4
5
6
7
8
GET /test_search_index/_search
{
"query": {
"match": {
"username": "alfred way"
}
}
}

如服务器会返回以下3个文档:took: 查询耗时、hits.total:返回的文档个数、_score:文档相关度得分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"took" : 52,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.7268042,
"hits" : [
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "4",
"_score" : 0.7268042,
"_source" : {
"username" : "alfred junior way",
"job" : "ruby engineer",
"age" : 23,
"birth" : "1989-08-07",
"isMarried" : false
}
},
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "1",
"_score" : 0.5753642,
"_source" : {
"username" : "alfred way",
"job" : "java engineer",
"age" : 18,
"birth" : "1990-01-02",
"isMarried" : false
}
},
{
"_index" : "test_search_index",
"_type" : "doc",
"_id" : "2",
"_score" : 0.22920427,
"_source" : {
"username" : "alfred",
"job" : "java senior engineer and java specialist",
"age" : 28,
"birth" : "1980-05-07",
"isMarried" : true
}
}
]
}
}

通过设置match query的operator参数可以控制单词间的匹配关系,可选项为or和and,默认为or。

如下查询,返回的是username字段中同时包含alfred和way的文档:

1
2
3
4
5
6
7
8
9
10
11
GET /test_search_index/_search
{
"query": {
"match": {
"username": {
"query": "alfred way",
"operator": "and"
}
}
}
}

通过设置minimum_should_match参数可以控制需要匹配的单词数。

如下查询,返回的是job字段至少包含java、ruby、engineer的2个以上单词匹配的文档:

1
2
3
4
5
6
7
8
9
10
11
GET /test_search_index/_search
{
"query": {
"match": {
"job": {
"query": "java ruby engineer",
"minimum_should_match": 2
}
}
}
}
match phrase query

按词语查询,对待查询的词语有顺序要求:

如下查询的是job字段包含"java engineer"的文档:

1
2
3
4
5
6
7
8
GET /test_search_index/_search
{
"query": {
"match_phrase": {
"job": "java engineer"
}
}
}

通过slop参数可以控制单词间的间隔:

如下查询的是job字段java和engineer相隔0个或1个单词的文档,如:java engineerjava senior engineer

1
2
3
4
5
6
7
8
9
10
11
GET /test_search_index/_search
{
"query": {
"match_phrase": {
"job": {
"query": "java engineer",
"slop": 1
}
}
}
}
query string query

类似于 URI search中的q参数查询:

如下查询的是username字段中包含alfred和way的文档:

1
2
3
4
5
6
7
8
9
GET /test_search_index/_search
{
"query": {
"query_string": {
"default_field": "username",
"query": "alfred AND way"
}
}
}

通过fields参数可以设置对多个字段进行查询:

如下查询的是username和job字段,条件是包含alfred或同时包含java和ruby:

1
2
3
4
5
6
7
8
9
10
11
12
GET /test_search_index/_search
{
"query": {
"query_string": {
"fields": [
"username",
"job"
],
"query": "alfred OR (java AND ruby)"
}
}
}
simple query string query

类似于query string,但是会忽略掉错误的查询语法,并且仅支持部分的查询语法。不能使用 AND,OR,NOT等关键词:

  • + 代替 AND

  • | 代替 OR

  • - 代替 NOT

如下查询的username字段中包含alfred和way的文档:

1
2
3
4
5
6
7
8
9
GET /test_search_index/_search
{
"query": {
"simple_query_string": {
"query": "alfred + way",
"fields": ["username"]
}
}
}
term query

将查询语句作为整个单词进行查询,即不对查询语句做分词。

如下查询的是username字段中包含alfred的文档:

1
2
3
4
5
6
7
8
GET /test_search_index/_search
{
"query": {
"term": {
"username": "alfred"
}
}
}

如下查询的是username字段中包含"alfred way"的文档,返回的文档将会是0个,因为username倒排索引并没有存储"alfred way"词语,而是存储的是"alfred way"分词后的结果:

1
2
3
4
5
6
7
8
GET /test_search_index/_search
{
"query": {
"term": {
"username": "alfred way"
}
}
}
terms query

一次传入多个单词进行查询。

如下查询的是username字段中包含alfred和way的文档:

1
2
3
4
5
6
7
8
9
10
11
GET /test_search_index/_search
{
"query": {
"terms": {
"username": [
"alfred",
"way"
]
}
}
}
range query

范围查询主要针对日期和数值类型:gt(大于)、gte(大于等于)、lt(小于)、lte(小于等于)

如下查询的是age在10-20之间的文档:

1
2
3
4
5
6
7
8
9
10
11
GET /test_search_index/_search
{
"query": {
"range": {
"age": {
"gte": 10,
"lte": 20
}
}
}
}

如下查询的是birth大于等于1990-01-01的文档:

1
2
3
4
5
6
7
8
9
10
GET /test_search_index/_search
{
"query": {
"range": {
"birth": {
"gte": "1990-01-01"
}
}
}
}
Date Math

Date Math是针对日期的一种更友好的计算格式,可以实现最近几天、最近几个小时等功能:

now-1d

now表示当前时间,也可以是具体的日期,如:2018-01-01,使用具体日期的时候需要用||做隔离。-1d表示减一天、+1h表示加1个小时、/d将时间舍入到天

主要的时间单位有:y(year)、M(months)、w(week)、d(days)、h(hours)、m(minutes)、s(seconds)

假设now为2018-01-02 12:00:00,那么如下的计算结果实际为:

计算公式 实际结果
now+1h 2018-01-02 13:00:00
now-1h 2018-01-02 11:00:00
now-1h/d 2018-01-02 00:00:00
  • 2017-01-01||+1M/d:表示为 2017-02-01 00:00:00

如下查询的是birth距离当前日期30年的文档:

1
2
3
4
5
6
7
8
9
10
GET /test_search_index/_search
{
"query": {
"range": {
"birth": {
"gte": "now-30y"
}
}
}
}

如下查询的是birth距离2010年20年的文档:

1
2
3
4
5
6
7
8
9
10
GET /test_search_index/_search
{
"query": {
"range": {
"birth": {
"gte": "2010||-20y"
}
}
}
}
复合查询

复合查询是指包含字段类查询或复合查询的类型,主要有以下几种:

  • constant_score query

  • bool query

  • dis_max query

  • function_score query

  • boosting query

constant_score query

constant_score query将其内部的查询结果文档得分都设定为1或者boost的值,多用于结合bool查询实现自定义得分。

如下查询返回的结果中的_score将会设置为1.0

1
2
3
4
5
6
7
8
9
10
11
12
GET /test_search_index/_search
{
"query": {
"constant_score": {
"filter": {
"match": {
"username": "alfred"
}
}
}
}
}
bool query

bool query由一个或多个bool子句组成,主要包含以下4个:

filter 只过滤符合条件的文档,不计算相关性得分
must 文档必须符合must中的所有条件,会影响相关性得分
must_not 文档必须不符合must_not中的所有条件
should 文档可以符合should中的条件,会影响相关性得分
filter

filter查询只返回符合条件的文档而不关心相关性,不进行相关性算分。

  • es对filter有智能缓存,所以其执行效率很高

  • 做简单查询而不考虑相关性算分的时候,推荐使用filter代替query等

如下查询服务器返回的_score为0,表示不计算相关性算分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /test_search_index/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"username": "alfred"
}
}
]
}
}
}
must

must指定了必须符合的条件。

如下查询的username包含alfred并且job包含specialist关键词的文档列表,es会对结果进行相关性算分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"username": "alfred"
}
},
{
"match": {
"job": "specialist"
}
}
]
}
}
}
must_not

must_not是指排除must_not中符合条件的文档。

如下查询job中包含java关键词但不包含ruby关键词的文档列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"job": "java"
}
}
],
"must_not": [
{
"match": {
"job": "ruby"
}
}
]
}
}
}
should

should的使用有两种情况:

  • bool查询中只包含should,不包含must查询

  • bool查询中同时包含should和must查询

只包含should时,文档必须满足至少一个条件,可以通过minimum_should_match可以控制满足条件的个数或者百分比。

如下查询的是job中必须包含java、ruby、specialist两个或两个以上匹配的文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /test_search_index/_search
{
"query": {
"bool": {
"should": [
{"term": {"job": "java"}},
{"term": {"job": "ruby"}},
{"term": {"job": "specialist"}}
],
"minimum_should_match": 2
}
}
}

同时包含should和must时,文档不必满足should中的条件,但是如果满足条件,会增加相关性得分。

如下查询username包含alfred的文档,同时将job包含ruby的文档排在前面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /test_search_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"username": "alfred"
}
}
],
"should": [
{
"term": {
"job": "ruby"
}
}
]
}
}
}
query context 和 filter context的区别

当一个查询语句位于Query或者Filter上下文时,es执行的结果会不同,对比如下:

query_filter.png

count

用于返回符合条件的文档数:

1
2
3
4
5
6
7
8
GET /test_search_index/_count
{
"query": {
"match": {
"username": "alfred"
}
}
}
source filtering

过滤返回结果中_source的字段,节省网络开销。

可以在URL参数_source中指定返回的字段:

GET /test_search_index/_search?_source=username

禁止返回_source:

1
2
3
4
GET /test_search_index/_search
{
"_source": false
}

返回部分字段:

1
2
3
4
GET /test_search_index/_search
{
"_source": ["username", "age"]
}