ElasticSearch之TermQuery和MatchQuery
在日常使用ES的时候,经常会将TermQuery和MatchQuery混淆,不知道该使用什么查询。本文举例说明。
一、TermQuery
Term是表达语意的最小单位。搜索和利用统计语言模型进行自然语言处理都需要处理Term
特点:
- Term Query主要包括:Term Query、Range Query、Exists Query、Prefix Query、Wildcard Query
- 在ES中,Term查询,对输入不做分词处理。会将输入作为一个整体,基于这个整体在倒排索引中查找准确的词项,并且使用相关度算分公式为每个包含该词项的文档进行相关度算分。
- 可以通过Constant Score 将查询转换成一个Filtering,避免算分,利用缓存,提高性能
首先我们先创建索引并准备三条数据
put phone
put phone/_mapping
{
"properties": {
"name": {
"type": "text"
},
"desc":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword"
}
}
}
}
}
POST phone/_doc
{
"name": "apple",
"desc":"Apple MacBook Pro 14英寸 M1 Pro芯片(8核中央处理器 14核图形处理器) 16G 512G 深空灰 笔记本 MKGP3CHA"
}
POST phone/_doc
{
"name": "huawei",
"desc":"华为 HUAWEI nova 8 Pro 麒麟985 5G SoC芯片 8GB+128GB 绮境森林全网通5G手机套餐一(无充电器和数据线)"
}
POST phone/_doc
{
"name": "xiaomi",
"desc":"小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
1.验证Term查询
先使用term查询如下:
get phone/_search
{
"query":{
"term":{
"name":{
"value":"Apple"
}
}
}
}
结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
并没有查询到结果,前面我们说了,使用term查询的时候,会将文本进行全匹配,所以输入大写的Apple,查不到数据。
get phone/_search
{
"query":{
"term":{
"name":{
"value":"apple"
}
}
}
}
结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9808291,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "fK7i8n4BV9WTNY27xi5I",
"_score" : 0.9808291,
"_source" : {
"name" : "apple",
"desc" : "Apple MacBook Pro 14英寸 M1 Pro芯片(8核中央处理器 14核图形处理器) 16G 512G 深空灰 笔记本 MKGP3CHA"
}
}
]
}
}
使用term查询,指定name为存储的name,可以查询到数据。
2.Term特殊用法
我们知道,Term查询会将查询的文本作为一个单元去进行匹配,并不会做分词处理,但是text类型的字段,在写入时会做倒排索引分词。
所以当我们想对text类型的字段进行全匹配的时候,term查询是查不到结果的,如下:
get phone/_search
{
"query":{
"term":{
"desc":{
"value":"小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
}
}
返回结果
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
因为我们使用的是默认分词器,所以来验证下使用分词后的文本查询是否能够查询到结果
get phone/_search
{
"query":{
"term":{
"desc":{
"value":"8gb"
}
}
}
}
查询结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.64110327,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 0.64110327,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
},
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "hK718n4BV9WTNY27_i4z",
"_score" : 0.46457356,
"_source" : {
"name" : "huawei",
"desc" : "华为 HUAWEI nova 8 Pro 麒麟985 5G SoC芯片 8GB+128GB 绮境森林全网通5G手机套餐一(无充电器和数据线)"
}
}
]
}
}
很明显,匹配到了两条数据,携带_score
匹配度算分,根据_score
进行排序。
那么我们想要使用term对text进行精准匹配,需要怎么做呢? 还记得在Mapping中desc字段,设置了多字段类型。
那么就可以强制字段类型进行查询。
get phone/_search
{
"query":{
"term":{
"desc.keyword":{
"value":"小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
}
}
结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9808291,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 0.9808291,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
]
}
}
结论:
1.Term Query不会将查询文本进行分词。
2.如果想要使用完全匹配,可以采用ES中的多字段属性
3.Term Query会返回算分结果
3.跳过算分
- 将Query转为Filter,忽略算分计算,避免开销。
- Filter可以有效利用缓存
利用Constant Score将查询转为Filter
get phone/_search
{
"query":{
"constant_score": {
"filter": {
"term":{
"desc":{
"value":"8gb"
}
}
}
}
}
}
查询结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "hK718n4BV9WTNY27_i4z",
"_score" : 1.0,
"_source" : {
"name" : "huawei",
"desc" : "华为 HUAWEI nova 8 Pro 麒麟985 5G SoC芯片 8GB+128GB 绮境森林全网通5G手机套餐一(无充电器和数据线)"
}
},
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 1.0,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
]
}
}
可以看到ES没有对结果进行算分计算。
二、MatchQuery
基于全文本的查找:Match Query、Match Phrase Query、Query String Query
特点:
- 索引和搜索时都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个供查询的词项列表
- 查询的时候。会先对输入的文本进行分词,然后每个此项逐个进行查询,最终将结果进行合并。并为每个文档生成一个算分。
1.验证Match查询
get phone/_search
{
"query":{
"match":{
"desc":{
"query":"小米手机"
}
}
}
}
查询结果
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 3.0446715,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 3.0446715,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
},
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "hK718n4BV9WTNY27_i4z",
"_score" : 0.9291471,
"_source" : {
"name" : "huawei",
"desc" : "华为 HUAWEI nova 8 Pro 麒麟985 5G SoC芯片 8GB+128GB 绮境森林全网通5G手机套餐一(无充电器和数据线)"
}
}
]
}
}
可以看到查询结果不仅有小米手机,还有华为手机。因为默认分词器将输入文本小米手机
分词为小、米、手、机,而华为的desc匹配到了手和机,所以也被查询出来了
并且查询结果携带_score
的匹配度算分。
2.Operator提高精准度
但是上述我们查询的是小米手机,华为手机也被匹配到了,在某些业务场景下显然是不允许的。
所以我们可以使用Operator对查询文本分词进行控制!
get phone/_search
{
"query":{
"match":{
"desc":{
"query":"小米手机",
"operator":"AND"
}
}
}
}
查询结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 3.0446715,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 3.0446715,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
]
}
}
指定了operator为AND
,查询文本的分词需要全部被包含在内才能够被匹配到。
3.minimum_should_match提高精准度
minimum_should_match
可以指定匹配的分词数。
get phone/_search
{
"query":{
"match":{
"desc":{
"query":"小米手机",
"minimum_should_match": 2
}
}
}
}
查询结果
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 3.0446715,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 3.0446715,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
},
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "hK718n4BV9WTNY27_i4z",
"_score" : 0.9291471,
"_source" : {
"name" : "huawei",
"desc" : "华为 HUAWEI nova 8 Pro 麒麟985 5G SoC芯片 8GB+128GB 绮境森林全网通5G手机套餐一(无充电器和数据线)"
}
}
]
}
}
指定"minimum_should_match": 2
,只要字段中包含了匹配到了两个分词,及在默认分词器下小米手机四个字中的任意两字,就会被查询出来,如上结果,华为手机也被查询出来了。
那我们指定为3应该就查询不出来了吧?
get phone/_search
{
"query":{
"match":{
"desc":{
"query":"小米手机",
"minimum_should_match": 3
}
}
}
}
查询结果
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 3.0446715,
"hits" : [
{
"_index" : "phone",
"_type" : "_doc",
"_id" : "ha728n4BV9WTNY27BC62",
"_score" : 3.0446715,
"_source" : {
"name" : "xiaomi",
"desc" : "小米12X 骁龙870 黄金手感 6.28英寸视感屏 120Hz高刷 5000万超清主摄 67W快充 8GB+128GB 蓝色 5G手机 8GB"
}
}
]
}
}
显然,华为手机的描述中,只能匹配到手和机这两个词条,我们指定最小匹配数为3就读不出来了。
4.Match Query的查询过程
由上述实践可知,MatchQuery的查询过程如下,首先对查询文本进行分词,在desc的倒排索引中,将分词后的每个词条进行查询,最终汇总得分结果进行排序,然后返回。
三、小结
- Term Query 和 Match Query 的区别:TermQuery不做分词处理,MatchQuery做分词处理。
- 如果有全文本匹配的场景,可以在Mapping中将字段设置为多类型 text+keyword
- 利用ES提供的参数来增加查询的精准度
- 复合查询 - Constant Score查询
即便对Keyword进行Term查询,同样会进行算分。但是既然都使用term来查询Keyword了,那么算分其实并不重要。
可以将查询转换为filter,取消相关性算分,并且能够利用缓存。达到一个提升性能的目的。