1 term搜索(关键词搜索)
term搜索不仅仅可以对keyword
类型的字段使用,也可以对text
类型的数据使用,前提是使用的搜索词必须要预先处理一下——不包含停止词并且都是小写(标准解析器),因为文档里面保存的text
字段分词后的结果,用term是可以匹配的。
1.1 exists(字段是否存在)
返回所有指定字段不为空的文档,比如这个字段对应的值是null
或者[]
或者没有为这个字段建立索引。
GET /_search
{
"query": {
"exists": {
"field": "user"
}
}
}
如果字段是空字符串""
或者包含null
的数组[null,"foo"]
,都会被当作字段存在。
这个方法可以用来搜索没有被索引的值或者不存在的值。
1.2 ids(根据指定id数组查询)
根据文档的_id
数组返回对应的文档信息
GET /_search
{
"query": {
"ids": {
"values": ["1","4","100"]
}
}
}
1.3 prefix(前缀查询)
返回所有包含以检索词为前缀的字段的文档。
GET /_search
{
"query": {
"prefix": {
"name": "ac"
}
}
}
返回所有以ac
开头的字段,比如acchu
、achu
、achar
等等
在某些场景下面比如搜索框里面,需要用户在输入内容的同时也要实时展示与输入内容前缀匹配的搜索结果,就可以使用prefix查询。为了加速prefix查询,还可以在设置字段映射的时候,使用index_prefixes
映射。ES会额外建立一个长度在2和5之间索引,在进行前缀匹配的时候效率会有很大的提高。
1.4 range(范围查询)
对字段进行范围的匹配。
GET /_search
{
"query": {
"range": {
"age": {
"gte": 10,
"lte": 20
}
}
}
}
搜索年龄在10(包含)和20(包含)之间的结果
1.5 term(准确匹配查询)
根据检索词来准确匹配字段。官方文档建议不要用term去搜索text
类型的字段,因为分析器的原因很有可能不会出现你想要的结果。但是直接使用term去搜索text
字段还是可以工作的,前提是明白为什么会返回这些数据。比如通过下面的搜索:
GET /_search
{
"query": {
"term": {
"name": {
"value": "accha"
}
}
}
}
如果name
字段是keyword
类型的,没有进行解析,那么只会匹配所有name
是accha
的文档。
如果name
字段是text
类型的,原字段经过分词、小写化处理之后,只能匹配到解析之后的单独token,比如使用标准解析器,这个搜索会匹配Accha Baccha
、so cute accha baccha
或者Accha Baccha Shivam
等字段。
1.6 terms(多关键字查询)
根据检索词列表来批量搜索文档,每个检索词在搜索的时候相当于or
的关系,只要一个匹配就行了。Elasticsearch最多允许65,536个term同时查询。
GET /_search
{
"query": {
"terms": {
"name": [
"accha",
"ghazali"
]
}
}
}
上面的查询会匹配name
字段为accha
和ghazali
的文档。
除了直接指定查询的term列表,还可以使用Terms lookUp功能,也就是指定某一个存在的文档的某一个字段(可能是数字、字符串或者列表)来作为搜索条件,进行terms搜索。
比如有一个文件index
是my_doc
,id
是10
,name
字段是term
并且值为accha
,搜索可以这样写:
{
"query": {
"terms": {
"name": {
"index": "my_doc",
"id": "10",
"path": "name"
}
}
}
}
这样就可以返回所有name
字段值是accha
的文档里,这个通常可以用来查询所有和某个文档某个字段重复的文档并且不需要提前知道这个字段的值是什么。
1.7 terms_set(多关键字查询)
terms_set和terms十分类似,只不过是多了一个最少需要匹配数量minimum_should_match_field
参数。当进行匹配的时候,只有至少包含了这么多的terms中的term的时候,才会返回对应的结果。
GET /_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": ["c++","java","php"],
"minimum_should_match_field": "required_match"
}
}
}
}
{
"name":"Jane Smith",
"programming_languages":[
"c++",
"java"
],
"required_matches":2
}
那么只有programming_languages
列表里面至少包含["c++", "java", "php"]
其中的2项才能满足条件
还可以使用minimum_should_match_script
脚本来配置动态查询
{
"query": {
"terms_set": {
"programming_languages": {
"terms": ["c++","java","php"],
"minimum_should_match_script": {
"source": "Math.min(params.num_terms, doc['required_matches'].value)"
}
}
}
}
}
其中params.num_terms
是在terms
字段中的元素的个数
1.8 wildcard(通配符查询)
通配符匹配,返回匹配包含通配符的检索词的结果。
目前只支持两种通配符:
?
:匹配任何单一的字符*
:匹配0个或者多个字符
在进行wildcard搜索的时候最好避免在检索词的开头使用*
或者?
,这会降低搜索性能。
GET /_search
{
"query": {
"wildcard": {
"name": {
"value": "acc*"
}
}
}
}
这个搜索会匹配acchu
、acche
或者accio父
2. text搜索
text
搜索实际上是针对被定义为text
类型的字段的搜索,通常搜索的时候不能根据输入的字符串的整体来理解,而是要预先处理一下,把搜索词变成小的token,再来查看每个token的匹配。
2.1 match(匹配查询)
查找和检索词短语匹配的文档,这些检索词在进行搜索之前会先被分析器解析,检索词可以是文本、数字、日期或者布尔值。match检索也可以进行模糊匹配。
GET /_search
{
"query": {
"match": {
"name": "nagesh acchu"
}
}
}
以上的查询会匹配NaGesh Acchu
、Acchu Acchu
和acchu
。系统默认是在分词后匹配任何一个token都可以完成匹配,如果修改operator
为AND
,则会匹配同时包含nagesh
和acchu
的字段。
GET /_search
{
"query": {
"match": {
"name": {
"query": "nagesh acchu",
"operator": "and"
}
}
}
}
上面这个查询就只会返回NaGesh Acchu
查询的时候也可以使用模糊查询,修改fuzziness
参数
GET /_search
{
"query": {
"match": {
"name": {
"query": "nagesh acchu",
"operator": "and",
"fuzziness": 1
}
}
}
}
上面的语句会匹配NaGesh Acchu
还有Nagesh Bacchu
2.2 match_phrase(短语匹配)
词组匹配会先解析检索词,并且标注出每个的token相对位置,搜索匹配的字段的必须包含所有的检索词的token,并且他们的相对位置也要和检索词里面相同。
GET /_search
{
"query": {
"match_phrase": {
"name": "Bade Acche"
}
}
}
这个搜索会匹配Bade Acche Lagte
,但是不会匹配Acche Bade Lagte
或者Bade Lagte Acche
。
如果我们不要求这两个单词相邻,希望放松一点条件,可以添加slop
参数,比如设置成1
,代表两个token之间相隔的最多的距离(最多需要移动多少次才能相邻)。下面的查询语句会匹配Bade Lagte Acche
GET /_search
{
"query": {
"match_phrase": {
"name": {
"query": "Bade Acche",
"slop": 1
}
}
}
}
2.3 multi_match(多字段匹配)
multi_match可以同时对多个字段进行查询匹配,ES支持很多种不同的查询类型比如best_fields
(任何字段match检索词都表示匹配成功)、phrase
(用match_phrase
代替match
)还有cross_field
(交叉匹配,通常用在所有的token必须在至少一个字段中出现)等等
下面是普通的best_fields
的匹配
GET /_search
{
"query": {
"multi_match": {
"query": "acchu",
"fields": [
"name",
"intro"
]
}
}
}
只要name
或者intro
字段任何一个包含acchu
都会完成匹配。
如果使用cross_fields
匹配如下
GET /_search
{
"query": {
"multi_match": {
"query": "call acchu",
"type": "cross_fields",
"fields": [
"name",
"intro"
],
"operator": "and"
}
}
}
上面的匹配需要同时满足下面两个条件:
name
中出现call
或intro
中出现call
name
中出现acchu
或intro
中出现acchu
所以这个查询能够匹配name
包含acchu
和intro
包含call
的文档,或者匹配name
同时包含call
和acchu
的文档。
2.4 query_string(查询语句)
输入一个查询语句,返回和这个查询语句匹配的所有的文档。
这个查询语句不是简单的检索词,而是包含特定语法的的搜索语句,里面包含操作符比如AND
和OR
,在进行查询之前会被一个语法解析器解析,转化成可以执行的搜索语句进行搜索。用户可以生成一个特别复杂的查询语句,里面可能包含通配符、多字段匹配等等。在搜索之前ES会检查查询语句的语法,如果有语法错误会直接报错。
GET /_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "acchu AND nagesh"
}
}
}
query_string里面还支持更加复杂的写法:
name: acchu nagesh
:查询name
包含acchu
和nagesh
其中的任意一个book.\*:(quick OR brown)
:book
的任何子字段比如book.title
和book.content
,包含quick
或者brown
_exists_: title
:title
字段包含非null
值name: acch*
:通配符,匹配任何acch
开头的字段name:/joh?n(ath[oa]n)/
:正则表达式,需要把内容放到两个斜杠/
中间name: acch~
:模糊匹配,默认编辑距离为2,不过80%的情况编辑距离为1就能解决问题name: acch~1
count:[1 TO 5]
:范围查询,或者count: >10
下面的查询允许匹配多个字段,字段之间时OR
的关系
GET /_search
{
"query": {
"query_string": {
"fields": [
"name",
"intro"
],
"query": "nagesh"
}
}
}
2.5 simple_query_string
和上面的query_string类似,但是使用了更加简单的语法。使用了下面的操作符:
+
表示AND
操作|
表示OR
操作-
表示否定"
用于圈定一个短语*
放在token的后面表示前缀匹配()
表示优先级~N
放在token后面表示模糊查询的最大编辑距离fuzziness
~N
放在phrase后面表示模糊匹配短语的slop
值
GET /_search
{
"query": {
"simple_query_string": {
"query": "acch* + foll~2 + -Karen",
"fields": [
"intro"
]
}
}
}
上面的搜索相当于搜索包含前缀为acch
的、和foll
编辑距离最大是2的并且不包含Karen
的字段,这样的语句会匹配call me acchu
或者acchu follow me
3. 比较少用的查询
3.1 fuzzy(模糊查询)
fuzzy查询是一种模糊查询,会根据检索词和检索字段的编辑距离(Levenshtein Distance)来判断是否匹配。一个编辑距离就是对单词进行一个字符的修改,这种修改可能是
- 修改一个字符,比如
box
到fox
- 删除一个字符,比如
black
到lack
- 插入一个字符,比如
sic
到sick
- 交换两个相邻的字符的位置,比如
act
到cat
在进行fuzzy搜索的时候,ES会生成一系列的在特定编辑距离内的变形,然后返回这些变形的准确匹配。默认情况下,当检索词的长度在0..2
中间时,必须准确匹配;长度在3..5
之间的时候,编辑距离最大为1;长度大于5
的时候,最多允许编辑距离为2。
可以通过配置fuzziness
修改最大编辑距离,max_expansions
修改最多的变形的token的数量
比如搜索是以下条件的时候:
GET /_search
{
"query": {
"fuzzy": {
"name": "Accha"
}
}
}
返回结果有Iccha
、AccHa
、accha
还有ccha
3.2 regexp
正则表达式匹配。通过正则表达式来寻找匹配的字段,lucene
会在搜索的时候生成有限状态机,其中包含很多的状态,默认的最多状态数量是10000
GET /_search
{
"query": {
"regexp": {
"name": "ac.*ha"
}
}
}
这个搜索会匹配achha
、achintha
还有achutha
3.3 interval
返回按照检索词的特定排列顺序排列的文档。这个查询比较复杂,这里只是简单的介绍,详细的介绍可以看官方文档
比如我们想查询同时包含raj
和nayaka
的字段并且ray
正好在nayaka
前面,查询语句如下:
POST /_search
{
"query": {
"intervals": {
"name": {
"match": {
"query": "raj nayaka",
"max_gaps": 0,
"ordered": true
}
}
}
}
}
上面的查询会匹配Raj Nayaka Acchu Valmiki
和Yateesh Raj Nayaka
。
如果把ordered:true
去掉,就会匹配nayaka raj
。
如果把max_gaps:0
去掉,系统会用默认值-1
也就是没有距离要求,就会匹配Raj Raja nayaka
或者Raj Kumar Nayaka
其中有两个关键词ordered
和max_gaps
分别用来控制这个筛选条件是否需要排序以及两个token之间的最大间隔
3.4 match_bool_prefix
match_bool_prefix会解析检索词,然后生成一个bool复合检索语句。如果检索词由很多个token构成,除了最后一个会进行prefix匹配,其他的会进行term匹配。
比如使用nagesh ac
进行match_bool_prefix搜索
GET /_search
{
"query": {
"match_bool_prefix": {
"name": "nagesh ac"
}
}
}
上面的查询会匹配Nagesh Nagesh
、Rakshith Achar
或者ACoco
实际查询等价于
GET /_search
{
"query": {
"bool": {
"should": [
{
"term": {
"name": {
"value": "nagesh"
}
}
},
{
"prefix": {
"name": {
"value": "ac"
}
}
}
]
}
}
}
3.5 match_phrase_prefix
match_phrase_prefix相当于是结合了match_bool_prefix和match_phrase。ES会先解析检索词,分成很多个token,然后除去最后一个token,对其他的token进行match_phrase的匹配,即全部都要匹配并且相对位置相同;对于最后一个token,需要进行前缀匹配并且匹配的这个单词在前面的match_phrase匹配的结果的后面。
GET /_search
{
"query": {
"match_phrase_prefix": {
"name": "acchu ac"
}
}
}
上面的查询能够匹配Acchu Acchu1
和Acchu Acchu Papu
,但是不能匹配acc acchu
或者acchu pa
3.6 common
common查询会把查询语句分成两个部分,较为重要的分为一个部分(这个部分的token通常在文章中出现频率比较低),不那么重要的为一个部分(出现频率比较高,以前可能被当作停止词),然后分别用low_freq_operator
、high_freq_operator
以及minimum_should_match
来控制这些语句的表现。
在进行查询之前需要指定一个区分高频和低频词的分界点,也就是cutoff_frequency
,它既可以是小数比如0.001
代表该字段所有的token的集合里面出现的频率也可以是大于1
的整数代表这个词出现的次数。当token的频率高于这一个阈值的时候,他就会被当作高频词。
GET /_search
{
"query": {
"common": {
"body": {
"query": "nelly the elephant as a cartoon",
"cutoff_frequency": 0.001,
"low_freq_operator": "and"
}
}
}
}
其中高频词是the
、a
和as
,低频词是nelly
、elephant
和cartoon
,上面的搜索大致等价于下面的查询
GET /_search
{
"query": {
"bool": {
"must": [
{"term": {"body": "nelly"}},
{"term": {"body": "elephant"}},
{"term": {"body": "cartoon"}}
],
"should": [
{"term": {"body": "the"}},
{"term": {"body": "as"}},
{"term": {"body": "a"}}
]
}
}
}
但是第一个查询的效率要优于第二个,因为common语句有性能上的优化,只有重要的token匹配之后的文档,才会在不重要的文档的查询时候计算_score
;不重要的token在查询的时候不会计算_score
总结
Elasticsearch提供了强大的搜索功能,使用query匹配可以进行相关性的计算排序但是filter可能更加适用于大多数的过滤查询的情况,如果用户对于标准解析器不太满意可以自定义解析器或者第三方解析器比如支持中文的IK解析器。
在进行搜索的时候一定要注意搜索keyword和text字段时候的区别,使用term相关的查询只能匹配单个的token但是使用text相关的搜索可以利用前面的term搜索进行组合查询,text搜索更加灵活强大,但是性能相对差一点。