match query
是ES中常见的一种全文搜索,该查询可以接受文本、数字、日期或者布尔类型,不过它在做查询之前会对查询的文本进行分词。
所谓的分词就是将文本拆分成一个个的词,对于英文而言通常通过空格符号拆分就行。但是对于中文而言,显然不是很好的处理方式。如果对这方面有兴趣,可以关注公众号,在我之前的文章中有相关内容介绍。
简单示例
在介绍简单使用的时候首先构建一些测试数据,示例代码如下:
POST _bulk
{ "index" : { "_index" : "doc"} }
{ "content":"what is java?"}
{ "index" : { "_index" : "doc"} }
{ "content":"Java is a programming language and computing platform first released by Sun Microsystems in 1995"}
{ "index" : { "_index" : "doc"} }
{"content":"java is programming language"}
{ "index" : { "_index" : "doc"} }
{"content":"Go is programming language"}
下面我们实现一个最简单的搜索,示例代码如下:
GET doc/_search
{
"query": {
"match": {
"content": "java programming language"
}
}
}
最后结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 1.2698221,
"hits" : [
{
"_index" : "doc",
"_type" : "_doc",
"_id" : "PtPKxoQB5b0eqQ47tMmg",
"_score" : 1.2698221,
"_source" : {
"content" : "java is programming language"
}
},
{
"_index" : "doc",
"_type" : "_doc",
"_id" : "P9PKxoQB5b0eqQ47tMmg",
"_score" : 0.8465481,
"_source" : {
"content" : "Go is programming language"
}
},
{
"_index" : "doc",
"_type" : "_doc",
"_id" : "PdPKxoQB5b0eqQ47tMmg",
"_score" : 0.6971004,
"_source" : {
"content" : "Java is a programming language and computing platform first released by Sun Microsystems in 1995"
}
},
{
"_index" : "doc",
"_type" : "_doc",
"_id" : "PNPKxoQB5b0eqQ47tMmg",
"_score" : 0.45743963,
"_source" : {
"content" : "what is java?"
}
}
]
}
}
从结果可以看出,所有的文档都被搜索出来了。在执行该查询时,match query
会将搜索的文本进行分词,在我们的示例中,它会把文本拆分为java
、programming
和language
三个词。同时match query
的查询类型是boolean
,你可以理解为该查询中有三个子查询,每个子查询对应的搜索内容就是拆分之后的词。默认情况下,子查询之间的关系为OR
,也就是只要文档匹配到任意一个子查询,文档都能被搜索出来。
参数详解
上面的示例只是一个最简单的查询,实际上match query
还支持许多参数,通过修改这些参数可以实现更复杂的功能。
analyzer
分析器参数。在创建索引时我们可以在mapping
中指定字段的类型和分析器。该分词器包括索引时分析器和搜索时分析器,其分别由analyzer
和search_analyzer
控制,默认情况下不指定使用的是standard
。而在搜索文本的同时,我们也可以指定分析器。该分析器是对搜索的文本进行分词。如果在我们没有指定的情况下,该分析器保持跟查询字段一致。大多数情况下,该值不需要设置使用默认值即可。如果设置成不一致,可能会导致查询有问题。
示例中使用的
ik
分析器需要安装,它不是ES中内置的分析器。
PUT test_doc
{
"mappings": {
"properties": {
"content":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
POST _bulk
{ "index" : { "_index" : "test_doc"} }
{ "content":"今天天气不错"}
{ "index" : { "_index" : "test_doc"} }
{ "content":"天气预报说明天是阴天"}
上面示例我们创建了一个新的索引并且为其设置了分析器为ik_max_word
,另外插入了部分测试数据,下面使用match query
搜索明天天气
信息,查询示例如下:
GET test_doc/_search
{
"query": {
"match": {
"content":{
"query": "明天天气",
"analyzer": "ik_max_word"
}
}
}
}
查询结果如下:
{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.9395274,
"hits" : [
{
"_index" : "test_doc",
"_type" : "_doc",
"_id" : "CdPhxoQB5b0eqQ47K8qN",
"_score" : 0.9395274,
"_source" : {
"content" : "今天天气不错"
}
},
{
"_index" : "test_doc",
"_type" : "_doc",
"_id" : "CtPhxoQB5b0eqQ47K8qN",
"_score" : 0.8195878,
"_source" : {
"content" : "天气预报说明天是阴天"
}
}
]
}
}
可以发现文档都能被搜索到,现在我们修改搜索时的分析器改成standard
,再次执行搜索如下:
GET test_doc/_search
{
"query": {
"match": {
"content":{
"query": "明天天气",
"analyzer": "standard"
}
}
}
}
执行该查询发现一个文档都没搜索到。之所以搜索不到,是因为ik_max_word
和standard
分析器分词结果不一致导致的。使用_analyze
API对搜索条件分词对比如下:
POST test_doc/_analyze
{
"text":"明天天气",
"analyzer": "ik_max_word"
}
{
"tokens" : [
{
"token" : "明天",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "天天",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "天气",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
}
]
}
POST test_doc/_analyze
{
"text":"明天天气",
"analyzer": "standard"
}
{
"tokens" : [
{
"token" : "明",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "天",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "天",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "气",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
}
]
}
从分词结果可以看出,它们的分词结果不一致所以导致搜索不到对应的文档。所以大多数情况下该值最好保持一致。
auto_generate_synonyms_phrase_query
是否为近义词生成短语查询,该值默认为true
。
了解该属性之前需要先了解近义词查询相关内容。
首先构建测试数据,示例代码如下:
PUT /test_doc1
{
"settings": {
"analysis": {
"filter": {
"my_synonym":{
"type":"synonym_graph",
"expand":true,
"synonyms":["ny,new york"]
}
},
"analyzer": {
"my_synonym_analyzer":{
"tokenizer":"standard",
"filter":["my_synonym"]
}
}
}
},
"mappings": {
"properties": {
"content":{
"type":"text",
"analyzer": "standard"
}
}
}
}
POST _bulk
{ "index" : { "_index" : "test_doc1"} }
{ "content":"ny in usa"}
{ "index" : { "_index" : "test_doc1"} }
{ "content":"new time is york"}
{ "index" : { "_index" : "test_doc1"} }
{ "content":"new york in usa"}
上面示例代码中创建了一个新的索引tes_doc1
,并且该索引中还自定义了一个分析器用来实现近义词搜索,最后插入了部分测试数据。
GET test_doc1/_search
{
"query": {
"match": {
"content": {
"query": "ny",
"analyzer": "my_synonym_analyzer",
"auto_generate_synonyms_phrase_query": "true"
}
}
}
}
该查询结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0596458,
"hits" : [
{
"_index" : "test_doc1",
"_type" : "_doc",
"_id" : "R9M9x4QB5b0eqQ47gc3d",
"_score" : 1.0596458,
"_source" : {
"content" : "ny in usa"
}
},
{
"_index" : "test_doc1",
"_type" : "_doc",
"_id" : "SdM9x4QB5b0eqQ47gc3d",
"_score" : 0.90630186,
"_source" : {
"content" : "new york in usa"
}
}
]
}
}
因为ny
是new york
的简称,所以文档1和3被查询出来了。现在修改auto_generate_synonyms_phrase_query
为false
,再次执行结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0596458,
"hits" : [
{
"_index" : "test_doc1",
"_type" : "_doc",
"_id" : "R9M9x4QB5b0eqQ47gc3d",
"_score" : 1.0596458,
"_source" : {
"content" : "ny in usa"
}
},
{
"_index" : "test_doc1",
"_type" : "_doc",
"_id" : "SNM9x4QB5b0eqQ47gc3d",
"_score" : 0.90630186,
"_source" : {
"content" : "new time is york"
}
},
{
"_index" : "test_doc1",
"_type" : "_doc",
"_id" : "SdM9x4QB5b0eqQ47gc3d",
"_score" : 0.90630186,
"_source" : {
"content" : "new york in usa"
}
}
]
}
}
从结果可以发现文档2被查询出来了,这是因为文档2中同时包含了new
和york
这两个词。之前之所以没查出来是因为new york
是执行了一个短语查询,这就要求这两个词必须是挨着的,中间不能带有别的词。
所以在实际业务开发中,该属性不推荐修改,修改之后可能导致召回率太高,但是搜索结果不太准确。
fuzziness
了解该参数前需要了解什么是模糊查询,例如我们在搜索java
时不小心打成了jave
,但是我们想搜索到java
相关内容,此时就需要模糊查询了,我们只需要编辑一次即把e
改成a
就可以完成了。而fuzziness
就是用来控制最大编辑距离的,默认情况下没有开启模糊查询,相当于fuzzuness=0
。
它的值可以是数字,例如0
,1
,2
,注意只能是这三个数字,超过之后将不支持。同时还支持AUTO
模式。AUTO模式的写法如下:
AUTO:lowDistance,highDistance
例如当fuzziness
的值为AUTO:3,5
时,它代表的意思如下:
-
长度小于3的词可编辑次数为0次
-
长度大于等于3且小于5时允许的编辑次数为1次
-
长度大于等于5时可编辑次数为2次
例如下面的查询
GET doc/_search
{
"query": {
"match": {
"content": {
"query": "it",
"fuzziness": "AUTO:3,5"
}
}
}
}
该查询没有结果返回,因为it
的长度为2所以编辑次数为0,所以无法找到含有is
的文档内容。
GET doc/_search
{
"query": {
"match": {
"content": {
"query": "jave",
"fuzziness": "AUTO:3,5"
}
}
}
}
搜索jave
时可以搜索出所有包含java
的文档,因为jave
只需要修改一次就可以变成java
。
max_expansions
查询扩展的最大字词数。该参数可能不太好理解,它的意思是我们使用fuzziness时可能会有很多词被匹配到。这个参数就是用来控制最大fuzziness匹配到的最大词数。可能语言表示不太好理解,直接看使用示例。
POST _bulk
{ "index" : { "_index" : "test_doc2"} }
{ "content":"abaa"}
{ "index" : { "_index" : "test_doc2"} }
{ "content":"abbb"}
{ "index" : { "_index" : "test_doc2"} }
{ "content":"abcc"}
{ "index" : { "_index" : "test_doc2"} }
{ "content":"abdd"}
上面我们构建了4个ab
开头的单词,现在使用fuzziness参数进行查询,示例代码如下:
GET test_doc2/_search
{
"query": {
"match": {
"content": {
"query": "abss",
"fuzziness": 2
}
}
}
}
该查询可以将所有文档全部查出来,现在我们增加max_expansions
为2的设置,查询如下:
GET test_doc2/_search
{
"query": {
"match": {
"content": {
"query": "abss",
"fuzziness": 2,
"max_expansions": 2
}
}
}
}
最后响应的结果如下:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.6019864,
"hits" : [
{
"_index" : "test_doc2",
"_type" : "_doc",
"_id" : "utOiyIQB5b0eqQ47-dlO",
"_score" : 0.6019864,
"_source" : {
"content" : "abaa"
}
},
{
"_index" : "test_doc2",
"_type" : "_doc",
"_id" : "u9OiyIQB5b0eqQ47-dlO",
"_score" : 0.6019864,
"_source" : {
"content" : "abbb"
}
}
]
}
}
原本未设置之前可以搜索到全部文档,现在只能搜索到2个,这就是max_expansions
的作用。默认情况下该值为50
,我这里的试验环境shard数量为1,如果在多shard的情况下,查询的结果并不能用来验证该值是否生效,因为每个shard中term可能不太一样。
prefix_length
模糊匹配的开头多少个字符数保持不变。例如我们在搜索java
的时候,大多数情况下开头一般不会出错,出错的一般是后面的内容。这个参数就是用来确认开头多少个字不参与fuzziness的。例如下面示例:
GET test_doc2/_search
{
"query": {
"match": {
"content": {
"query": "aaaa",
"fuzziness": 2,
"prefix_length": 0
}
}
}
}
上面查询的结果可以找到如下文档:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9029796,
"hits" : [
{
"_index" : "test_doc2",
"_type" : "_doc",
"_id" : "utOiyIQB5b0eqQ47-dlO",
"_score" : 0.9029796,
"_source" : {
"content" : "abaa"
}
}
]
}
}
现在修改prefix_length=2
,再次执行查询将找不到任何文档。
fuzzy_transpositions
该值是用来处理交换位置的。例如ab
变成ba
,如果按照编辑次数算法编辑次数为2,但是如果设置为true
,就认为编辑次数为1。默认情况下,该值为true
。
GET test_doc2/_search
{
"query": {
"match": {
"content": {
"query": "baaa",
"fuzziness": 1
}
}
}
}
结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9029796,
"hits" : [
{
"_index" : "test_doc2",
"_type" : "_doc",
"_id" : "utOiyIQB5b0eqQ47-dlO",
"_score" : 0.9029796,
"_source" : {
"content" : "abaa"
}
}
]
}
}
如果修改fuzzy_transpositions
为false
,示例如下:
GET test_doc2/_search
{
"query": {
"match": {
"content": {
"query": "baaa",
"fuzziness": 1,
"fuzzy_transpositions": "false"
}
}
}
}
该查询将无法查找到abaa
,因为此时编辑距离为2。
fuzzy_rewrite
用于重写查询方法。如果fuzziness参数不为0时,则使用默认方法top_terms_blended_freqs_${max_expansions}
。
lenient
该值默认为false
,表示用来在查询时如果数据类型不匹配且无法转换时会报错。如果设置为true
则会忽略报错。
operator
match query
是一种Boolean类型的查询。例如在前面简单示例
中的例子,查询java programming language
,只要文档中包含java
、programming
和language
中的任意一个都能被查询出来,相当于它们之间的关系为OR
。现在我们通过设置operator
为and
示例如下:
GET doc/_search
{
"query": {
"match": {
"content": {
"query": "java programming language",
"operator": "and"
}
}
}
}
该查询结果中what is java?
和Go is programming language
将无法被搜索到,因为他们并没有同时存在这三个词,相当于它们之间的关系为and
。
minimum_should_match
使用operator
太绝对,它只能是全部匹配或者任意匹配一个。而minimum_should_match
可以按比例或者固定个数匹配。
GET doc/_search
{
"query": {
"match": {
"content": {
"query": "java programming language",
"minimum_should_match": 2
}
}
}
}
上面示例的意思是,三个词只需要匹配到任意2个即可。所以最后就只有what is java?
无法匹配。
zero_terms_query
如果分析器中有停用词处理,搜索文本中只存在停用词,最后分词结果为空。该选项就是用来处理这种情况的,默认情况下该值为none
,意味着将没有任何结果返回。如果为all
时将相当于match_all
查询。
GET doc/_search
{
"query": {
"match": {
"content": {
"query": "is",
"analyzer": "stop",
"zero_terms_query": "none"
}
}
}
}
在英文中is
就是我们所说的停用词,这里我们设置搜索时分析器为stop
,执行查询结果发现任何文档都搜索不到。如果修改zero_terms_query
为all
,最后就相当于执行了match_all
。
cutoff_frequency
对于查询的词按出现频率可以分为低频词和高频次,通常情况下低频词更加重要,而高频词相对来说不是那么重要。例如在数据库相关的内容资料中,database
该词可能在每个文档中出现,如果搜索database index
文本内容,index
匹配出来的文档会更加符合我们的预期条件。
上面所说的database
在数据库相关文档中是一个高频词,但是在介绍编程语言的文档中,该词可能又是一个低频词。总结起来就是,高频词和低频词并不是绝对的,而是需要根据实际文档场景来决定。而我们这里的cutoff_frequency
就是用来区分高频词和低频词的。在查询的时候,低频词会组成一个查询,而高频词只会用来评分,而不参与匹配过程。这种做的好处就是在查询速度上得到巨大提升。例如对于一个高频词,可能大多数文档都存在,这将导致大多数文档都被匹配出来,最后对大量文档进行相关性打分排序。如果在数据量特别大的情况下查询性能将会特别的糟糕。
cutoff_frequency
可以设置一个0到1的小数来表示超过百分之多少则认为是高频词,也可以设置成一个大于或等于1的整型数字,用来表达在多少个文档中存在就表示是高频词。
POST _bulk
{ "index" : { "_index" : "test_doc3"} }
{ "content":"this is content"}
{ "index" : { "_index" : "test_doc3"} }
{ "content":"she is rose"}
{ "index" : { "_index" : "test_doc3"} }
{ "content":"he is jack"}
{ "index" : { "_index" : "test_doc3"} }
{ "content":"I like tom"}
上面是构建测试数据,现在我们查询is tom
,并且将cutoff_frequency
设置为2,表示在所有文档中出现次数找过2次就认为是高配词。
GET test_doc3/_search
{
"query": {
"match": {
"content": {
"query": "is tom",
"cutoff_frequency": 2
}
}
}
}
最后运行结果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.2039728,
"hits" : [
{
"_index" : "test_doc3",
"_type" : "_doc",
"_id" : "2dP7yIQB5b0eqQ47Odyq",
"_score" : 1.2039728,
"_source" : {
"content" : "I like tom"
}
}
]
}
}
从结果可以看出,is
并没有起到搜索查询的作用,这是因为is
在三个文档中出现过,而cutoff_frequency
为2,所以只会查询含有tom
内容的文档。
原文始发于微信公众号(一只菜鸟程序员):ES全文搜索-match query
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72811.html