高级查询
五、高级查询
在查询文档中,已经介绍了基本的查询。下面是高级查询:
1 DSL查询之match
DSL分为match
和trem
两种模式。
1.1 match
在前面简单介绍了match
的查询:
GET books/_doc/_search
{
"query": {
"match": {
"price": 50
}
}
}
将查询条件添加到match
中即可,而match
则是查询所有price
字段的值中为50
的结果。
1.2 match_all
除了按条件查询之外,我们还可以查询books
索引下的doc
类型中的所有文档,查询全部match_all
:
GET books/_doc/_search
{
"query": {
"match_all": {}
}
}
1.3 match_phrase
前面的查询中,我们只能获取文档中的关键字,首先创建一些示例:
PUT t1/_doc/1
{
"title": "中国是世界上人口最多的国家"
}
PUT t1/_doc/2
{
"title": "美国是世界上军事实力最强大的国家"
}
PUT t1/_doc/3
{
"title": "北京是中国的首都"
}
现在,当我们以中国
作为搜索条件,我们希望只返回和中国
相关的文档。我们首先来使用match
查询:
GET t1/_doc/_search
{
"query": {
"match": {
"title": "中国"
}
}
}
虽然如期的返回了中国
的文档。但是却把和美国
的文档也返回了,这并不是我们想要的。是怎么回事呢?因为这是elasticsearch在内部对文档做分词的时候,对于中文来说,就是一个字一个字分的,所以,我们搜中国
,中
和国
都符合条件,返回,而美国的国
也符合。
而我们认为中国
是个短语,是一个有具体含义的词。所以elasticsearch在处理中文分词方面比较弱势。我们可以用中文分词器来解决。
另外还有另一种办法解决,那就是使用短语查询:
GET t1/_doc/_search
{
"query": {
"match_phrase": {
"title": {
"query": "中国"
}
}
}
}
这里match_phrase
是在文档中搜索指定的词组,而中国
则正是一个词组,所以能获取到。
比如我们要想搜索中国
和世界
相关的文档,但又忘记其余部分了,此时可以使用match_phrase
指定分词之间的间隔:
GET t1/_doc/_search
{
"query": {
"match_phrase": {
"title": {
"query": "中国世界",
"slop": 2
}
}
}
}
这里的slop
相当于正则中的中国.*?世界
。这个间隔默认为0,如果不加会导致查询不到,指定为2就可以查到了。
1.4 match_phrase_prefix
凌晨2点半,单身狗小黑为了缓解寂寞,就准备搜索几个beautiful girl
来陪伴自己。但是由于英语没过2级,但单词beautiful
拼到bea
就不知道往下怎么拼了。这个时候,我们的智能搜索要帮他啊,elasticsearch就看自己的词库有啥是bea
开头的词,结果还真发现了两个:
PUT t3/_doc/1
{
"title": "maggie",
"desc": "beautiful girl you are beautiful so"
}
PUT t3/_doc/2
{
"title": "sun and beach",
"desc": "I like basking on the beach"
}
但这里用match
和match_phrase
都不太合适,因为小黑输入的不是完整的词。那怎么办呢?我们用match_phrase_prefix
来搞:
GET t3/_doc/_search
{
"query": {
"match_phrase_prefix": {
"desc": "bea"
}
}
}
这样就搜索到了前面插入的两条数据。
前缀查询是短语查询类似,但前缀查询可以更进一步的搜索词组,只不过它是和词组中最后一个词条进行前缀匹配(如搜这样的you are bea
)。应用也非常的广泛,比如搜索框的提示信息,当使用这种行为进行搜索时,最好通过max_expansions
来设置最大的前缀扩展数量,因为产生的结果会是一个很大的集合,不加限制的话,影响查询性能。
GET t3/_doc/_search
{
"query": {
"match_phrase_prefix": {
"desc": {
"query": "bea",
"max_expansions": 1
}
}
}
}
我们只需要记住,使用前缀查询会非常的影响性能,要对结果集进行限制,就加上这个max_expansions
参数。
1.5 multi_match
有时候需要多字段查询:现在,我们有一个50个字段的索引,我们要在多个字段中查询同一个关键字,该怎么做呢?
PUT t3/_doc/1
{
"title": "maggie is beautiful girl",
"desc": "beautiful girl you are beautiful so"
}
PUT t3/_doc/2
{
"title": "beautiful beach",
"desc": "I like basking on the beach,and you? beautiful girl"
}
我们先用原来的方法查询:
GET t3/_doc/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "beautiful"
}
},
{
"match": {
"desc": "beautiful"
}
}
]
}
}
}
bool
是布尔查询,后面会介绍。使用must
来限制两个字段(值)中必须同时含有关键字。这样虽然能达到目的,但是当有很多的字段,我们可以用multi_match
来做:
GET t3/_doc/_search
{
"query": {
"multi_match": {
"query": "beautiful",
"fields": ["title", "desc"]
}
}
}
我们将多个字段放到fields
列表中即可。以达到匹配多个字段的目的。也就是在title
和desc
中查询beautiful
关键字。
除此之外,multi_match
甚至可以当做match_phrase
和match_phrase_prefix
使用,只需要指定type
类型即可:
GET t3/_doc/_search
{
"query": {
"multi_match": {
"query": "gi",
"fields": ["title"],
"type": "phrase_prefix"
}
}
}
GET t3/_doc/_search
{
"query": {
"multi_match": {
"query": "girl",
"fields": ["title"],
"type": "phrase"
}
}
}
2 DSL查询之term
默认情况下,es在对文档分析期间(将文档分词后保存到倒排索引中),会对文档进行分词,比如默认的标准分析器会对文档进行:
- 删除大多数的标点符号。
- 将文档分解为单个词条,我们称为token。
- 将token转为小写。
完事再保存到倒排索引上,当然,原文件还是要保存一份的,而倒排索引使用来查询的。
例如Beautiful girl!
,在经过分析后是这样的了:
POST _analyze
{
"analyzer": "standard",
"text": "Beautiful girl!"
}
# 结果
["beautiful", "girl"]
而当在使用match查询时,elasticsearch同样会对查询关键字进行分析,
PUT w10
{
"mappings": {
"_doc":{
"properties":{
"t1":{
"type": "text"
}
}
}
}
}
PUT w10/_doc/1
{
"t1": "Beautiful girl!"
}
PUT w10/_doc/2
{
"t1": "sexy girl!"
}
GET w10/_doc/_search
{
"query": {
"match": {
"t1": "Beautiful girl!"
}
}
}
也就是对查询关键字Beautiful girl!
进行分析,得到["beautiful", "girl"]
,然后分别将这两个单独的token去索引中进行查询,结果就是将两篇文档都返回。这在有些情况下是非常好用的,但是,如果我们想查询确切的词怎么办?也就是精确查询,将Beautiful girl!
当成一个token而不是分词后的两个token。这就要用到了term查询了,term查询的是没有经过分析的查询关键字。
如果你要查询的字段类型(如上例中的字段t1
类型是text
)是text
(因为elasticsearch会对文档进行分析,上面说过),那么你得到的可能是不尽如人意的结果或者压根没有结果:
GET w10/_doc/_search
{
"query": {
"term": {
"t1": "Beautiful girl!"
}
}
}
如上面的查询,将不会有结果返回,因为索引w10
中的两篇文档在经过elasticsearch分析后没有一个分词是Beautiful girl!
,那此次查询结果为空也就好理解了。
所以,我们这里得到一个论证结果:不要使用term对类型是text的字段进行查询,要查询text类型的字段,请改用match查询。
再来一个示例:
GET w10/_doc/_search
{
"query": {
"term": {
"t1": "Beautiful"
}
}
}
答案是,没有结果返回!因为elasticsearch在对文档进行分析时,会经过小写!人家倒排索引上存的是小写的beautiful
,而我们查询的是大写的Beautiful
。
所以,想有结果要这样:
GET w10/_doc/_search
{
"query": {
"term": {
"t1": "beautiful"
}
}
}
那term查询可以查询哪些类型的字段呢,例如elasticsearch会将keyword类型的字段当成一个token保存到倒排索引上,你可以将term和keyword结合使用。
最后,要想使用term查询多个精确的值使用terms
(term+s)查询:
GET w10/_doc/_search
{
"query": {
"terms": {
"t1": ["beautiful", "sexy"]
}
}
}
3 排序sort
3.1 降序desc
比如想要根据age字段按照降序排序:
GET foo/_doc/_search
{
"query": {
"match": {
"title": "xxx"
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
3.2 升序asc
GET foo/_doc/_search
{
"query": {
"match": {
"title": "xxx"
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
]
}
注意:在排序的过程中,只能使用可排序的属性进行排序。可以排序的属性有数字和日期,使用其它的都不行。
4 分页from/size
将结果分页:
GET foo/_doc/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 2,
"size": 1
}
上例,首先以age
降序排序,查询所有。并且在查询的时候,添加两个属性from
和size
来控制查询结果集的数据条数。
- from:从哪开始查
- size:返回几条结果
你可以这样:from:2,size:2
意为从第2条开始返回两条数据。
如果这样写:siez:0
那么就返回0条结果。
学到这里,我们也可以看到,查询条件越来越多,开始仅是简单查询,慢慢增加条件查询,增加排序,对返回结果进行限制。所以,我们可以说:对于elasticsearch
来说,所有的条件都是可插拔的,彼此之间用,
分割。
5 布尔查询
布尔查询是最常用的组合查询,根据子查询的规则,只有当文档满足所有子查询条件时,elasticsearch引擎才将结果返回。布尔查询支持的子查询条件共4种:
- must(and)
- should(or)
- must_not(not)
- filter(条件过滤)
5.1 must
逻辑与(and)。
GET foo/_doc/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"from": "gu"
}
}
]
}
}
}
上例中,我们通过在bool
属性(字段)内使用must
来作为查询条件,那么条件是什么呢?条件同样被match
包围,就是from
为gu
的所有数据。这里需要注意的是must
字段对应的是个列表[]
,也就是说可以有多个并列的查询条件,一个文档满足各个子条件后才最终返回。
比如想要查询from
为gu
,并且age
为30
的数据:
GET foo/_doc/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"from": "gu"
}
},
{
"match": {
"age": 30
}
}
]
}
}
}
5.2 should
逻辑或(or)。
那么,如果要查询只要是from
为gu
或者tags
为闭月
的数据:
GET foo/_doc/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"from": "gu"
}
},
{
"match": {
"tags": "闭月"
}
}
]
}
}
}
5.3 must_not
逻辑非(not)。
查询from
既不是gu
并且tags
也不是可爱
,还有age
不是18
的数据:
GET foo/_doc/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"from": "gu"
}
},
{
"match": {
"tags": "可爱"
}
},
{
"match": {
"age": 18
}
}
]
}
}
}
5.4 filter
查询from
为gu
,age
大于25
的数据:
GET foo/_doc/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"from": "gu"
}
}
],
"filter": {
"range": {
"age": {
"gt": 25
}
}
}
}
}
}
这里就用到了filter
条件过滤查询,过滤条件的范围用range
表示,gt
表示大于。
过滤条件有:
- gt:大于
- lt:小于
- gte:大于等于
- lte:小于等于
想要查询一个范围内的数据,比如要查询from
是gu
,age
在25~30
之间的数据:
GET foo/_doc/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"from": "gu"
}
}
],
"filter": {
"range": {
"age": {
"gte": 25,
"lte": 30
}
}
}
}
}
}
同时使用lte
和gte
来限定范围。
另外,如果在filter
过滤条件中使用should
的话,结果可能不会尽如人意!建议使用must
代替。并且注意filter
工作于bool
查询内。比如我们将刚才的查询条件改一下,把filter
从bool
中挪出来,就会报错。
6 查询结果高亮显示
如果返回的结果集中很多符合条件的结果,我们想要让某些结果高亮显示。比如下面网站所示的那样,我们搜索elasticsearch
,在结果集中,将所有elasticsearch
高亮显示。
我们这样做:
GET foo/_doc/_search
{
"query": {
"match": {
"name": "石头"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}
使用highlight
属性来实现结果高亮显示,需要的字段名称添加到fields
内即可,es会自动将检索结果用标签包裹起来,用于在页面中渲染。返回的结果会变成这样
"highlight" : {
"name" : [
"<em>石</em><em>头</em>"
]
}
结果被<em></em>
标签包裹。如果不想用em
标签,也可以自定义标签。
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"highlight": {
"pre_tags": "<b class='key' style='color:red'>",
"post_tags": "</b>",
"fields": {
"from": {}
}
}
}
上例中,在highlight
中,pre_tags
用来实现我们的自定义标签的前半部分,在这里,我们也可以为自定义的标签添加属性和样式。post_tags
实现标签的后半部分,组成一个完整的标签。至于标签中的内容,则还是交给fields
来完成。
需要注意的是:自定义标签中属性或样式中的逗号一律用英文状态的单引号表示,应该与外部elasticsearch
语法的双引号区分开。
7 查询结果过滤
对查询结果进行过滤,使用_source
,比如只需要查看name
和age
两个属性,其他的不要:
GET foo/_doc/_search
{
"query": {
"match": {
"name": "xxx"
}
},
"_source": ["name", "age"]
}
8 聚合查询
聚合函数大家都不陌生,es中也没玩出新花样:
- avg
- max
- min
- sum
8.1 avg
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"aggs": {
"my_avg": {
"avg": {
"field": "age"
}
}
},
"_source": ["name", "age"]
}
上例中,首先匹配查询from
是gu
的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs
中,而my_avg
则是为查询结果起个别名,封装了计算出的平均值。那么,要查age
字段的平均值。最后对结果进行过滤,只返回name
和age
字段的数据以及年龄的平均值。
如果不想看都有哪些数据,只想看平均值,用size
即可:
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"aggs": {
"my_avg": {
"avg": {
"field": "age"
}
}
},
"size": 0,
"_source": ["name", "age"]
}
只需要在原来的查询基础上,增加一个size
就可以了,我们写上0,就是输出0条查询结果。
8.2 max
查最大值:
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"aggs": {
"my_max": {
"max": {
"field": "age"
}
}
},
"size": 0
}
8.3 min
查最小值
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"aggs": {
"my_min": {
"min": {
"field": "age"
}
}
},
"size": 0
}
8.4 sum
求总和:
GET foo/_doc/_search
{
"query": {
"match": {
"from": "gu"
}
},
"aggs": {
"my_sum": {
"sum": {
"field": "age"
}
}
},
"size": 0
}
8.5 分组查询
假如想要查询所有人的年龄段,并且按照15~20,20~25,25~30
分组,并且算出每组的平均年龄。
首先做出分组:
GET foo/_doc/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"age_group": {
"range": {
"field": "age",
"ranges": [
{
"from": 15,
"to": 20
},
{
"from": 20,
"to": 25
},
{
"from": 25,
"to": 30
}
]
}
}
}
}
上例中,在aggs
的自定义别名age_group
中,使用range
来做分组,field
是以age
为分组,分组使用ranges
来做,from
和to
是范围,我们根据需求做出三组。接下来,我们就要对每个小组内的数据做平均年龄处理。
GET foo/_doc/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"age_group": {
"range": {
"field": "age",
"ranges": [
{
"from": 15,
"to": 20
},
{
"from": 20,
"to": 25
},
{
"from": 25,
"to": 30
}
]
},
"aggs": {
"my_avg": {
"avg": {
"field": "age"
}
}
}
}
}
}
上例中,在分组下面,我们使用aggs
对age
做平均数处理,这样就可以了。
注意:一定要先查出结果,然后对结果使用聚合函数做处理。
本文来自博客园,作者:yyyz,转载请注明原文链接:https://www.cnblogs.com/yyyzyyyz/p/15700486.html