Match API:匹配搜索
很多时候我们在搜索的时候其实并不会去做精确匹配,一方面是没必要,一方面是浪费性能,可以说是吃力不讨好。
当数据索引的时候,Es会使用分词器对文本数据进行分词,并且统计每个词语出现的次数等等信息。当用户对文本数据进行检索的时候,Es会使用同样的分词器对输入内容进行分词,然后与文本内容进行匹配,根据统计信息给每一个词语打分,最后根据公式计算出相关性评分,并且返回相关性最高的 Top 文档。
Es支持全文搜索的API主要有以下几个:
match | 可以处理全文本,精确字段(日期,数字等等)。 |
match phrase | 短语匹配会将检索内容进行分词,这些词语必须全部出现在被搜索内容中,并且顺序必须一致,默认情况下,这些词必须都是连续的。 |
match phrase prefix | 类似 match phrase ,但是最后一个词项会作为前缀,并且匹配这个词项开头的任何词语。 |
multi match | 通过 multi match 可以在多个字段上执行相同的查询语句。 |
match boolean prefix | 结合bool查询和前缀查询。 |
match_all & match_none | 查询全部,和查询0条。 |
下面我们还是以订单表为例,进行全文搜索的使用分析。
本篇文章使用的
standard
分词器对文本进行分词处理,这个分词器会将文本按照单词切分并且转换为小写。
PUT order
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"sub_order_no": {
"type": "keyword"
},
"order_no": {
"type": "keyword"
},
"price":{
"type": "double"
},
"createTime":{
"type": "date"
}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
## 简单造几条数据
PUT /order/_doc/d17be93b-648f-44b7-993f-98e2ae329306
{
"id": "d17be93b-648f-44b7-993f-98e2ae329306",
"name": "iphone13",
"sub_order_no": [
"3b9ebe09-99d7-4c79-85a4-e99a721c24b5",
"86a38185-b100-4713-9a73-36c17f22fba4",
"8887aed3-ac31-4826-9b1b-0b02e549b4fb",
"47c735dd-735a-4a3e-a427-2b35f95c1ec3",
"7ca1fcd4-faf9-44ac-b524-3e2844701dc9"
],
"order_no": "d17be93b-648f-44b7-993f-98e2ae329306",
"price": 2634.6998447085434,
"createTime": 1688211961508
}
PUT /order/_doc/47c735dd-735a-4a3e-a427-2b35f95c1ec3
{
"id": "47c735dd-735a-4a3e-a427-2b35f95c1ec3",
"name": "xiaomi13",
"sub_order_no": [],
"order_no": "47c735dd-735a-4a3e-a427-2b35f95c1ec3",
"price": 23.50361213313873,
"createTime": 1688211961508
}
PUT /order/_doc/8887aed3-ac31-4826-9b1b-0b02e549b4fb
{
"id": "8887aed3-ac31-4826-9b1b-0b02e549b4fb",
"name": "huawei mate 50",
"sub_order_no": [],
"order_no": "8887aed3-ac31-4826-9b1b-0b02e549b4fb",
"price": 21.132645156267703,
"createTime": 1688211961508
}
PUT /order/_doc/86a38185-b100-4713-9a73-36c17f22fba4
{
"id": "86a38185-b100-4713-9a73-36c17f22fba4",
"name": "sanxing",
"sub_order_no": [],
"order_no": "86a38185-b100-4713-9a73-36c17f22fba4",
"price": 35.66395720954454,
"createTime": 1688211961508
}
PUT /order/_doc/7ca1fcd4-faf9-44ac-b524-3e2844701dc9
{
"id": "7ca1fcd4-faf9-44ac-b524-3e2844701dc9",
"name": "vivo",
"sub_order_no": [],
"order_no": "7ca1fcd4-faf9-44ac-b524-3e2844701dc9",
"price": 18.91113695347623,
"createTime": 1688211961508
}
PUT /order/_doc/3b9ebe09-99d7-4c79-85a4-e99a721c24b5
{
"id": "3b9ebe09-99d7-4c79-85a4-e99a721c24b5",
"name": "oppo phone is very good",
"sub_order_no": [],
"order_no": "3b9ebe09-99d7-4c79-85a4-e99a721c24b5",
"price": 9.281517420473545,
"createTime": 1688211961508
}
1.Match 匹配查询
匹配查询可以处理全文本、精确字段(日期、数字等)。
GET order/_search
{
"query": {
"match": {
"name": "iphone13 xiaomi13"
}
}
}
我们通过 Match API
进行了一次全文本字段的查询,我们查询名字中含有 iphone13 xiaomi13
的文档,系统匹配了 iphone13
和 xiaomi13
两个文档。
在进行全文本字段检索的时候,Match API
提供了operator
和 minimum_should_match
参数:
operator | 参数值可以为or 或者 and来控制检索词项间的关系,默认是or。所以上面的查询语句中只要出现了xiaomi13 或者iphone13 的文档都可以匹配上。 |
minimum_should_match | 可以指定词项的最少匹配个数,其值可以指定为某个具体的数字,但因为我们一般无法预估检索内容的词项数量,一般将其设置为一个百分比。 |
For Example:至少有百分之七十的词项匹配才会返回该文档。
GET order/_search
{
"query": {
"match": {
"name": {
"query": "oppo phone is very bad",
"operator": "or",
"minimum_should_match": "70%"
}
}
}
}
另外,除了处理全文本字段外,Match API
支持查询包含精确字段的文档。
For Example:匹配创建时间为 2023-07-01
的文档。
GET order/_search
{
"query": {
"match": {
"createTime": "2023-07-01"
}
}
}
Match API
是怎么样运行的呢? 匹配查询属于bool类型,这意味着他会对输入的数据进行分词,并且分词过程中会根据输入的数据构建出一个bool查询。运算符参数设置为 or
或者 and
来控制bool子句。可以使用 minimum_should_match
参数设置要匹配的可选 should
子句的最小数量。
另外可以设置分词器来控制哪个分词器对文本执行分析过程,默认为 Mapping
中指定的分词器 或默认搜索分词器。
可以将宽松参数设置为true,以忽略由数据类型不匹配导致的异常。比如尝试使用文本查询字符串数值字段,默认为 false
。
2.Match phrase 短语匹配
短语匹配会将检索内容进行分词,这些词语必须全部出现在被检索内容中,并且顺序必须一致,默认情况下这些词都必须连续。
For Example:
# 短语匹配
POST order/_search
{
"query": {
"match_phrase": {
# 换成 "oppo phone very is"是查询不到的
"name": "oppo phone is very"
}
}
}
当我们根据oppo is
搜索订单名字的时候搜索不到文档。因为没有订单的名字带有这个短语。这个时候可以通过 slop
参数指定词项之间的距离差值,即两个词项中可以含有多少个其他不相关的词语,默认值是0。
POST order/_search
{
"query": {
"match_phrase": {
"name": {
"query": "oppo is",
"slop": 1
}
}
}
}
当
slop
参数设置为1的时候oppo is
也可以搜索到文档,原因是文档中oppo phone is
中间的phone
因为不相关被跳过了。
3.Match phrase prefix 短语前缀匹配
match phrase prefix
与 match phrase
类似,但最后一个词项会作为前缀,并且匹配这个词项开头的任何词语。可以使用 max_expansions
参数来控制最后一个词项的匹配数量,此参数默认值为 50。
For Example:
# 匹配以 "oppo phone" 开头的短语
POST order/_search
{
"query": {
"match_phrase_prefix": {
"name": "oppo phone"
}
}
}
# 匹配以 "oppo phone" 开头的短语,每个分片最多匹配 2 个
POST order/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "oppo phone",
"max_expansions": 2
}
}
}
}
上面第一条SQL可以匹配订单名中含有 "oppo phone12"、"oppo phone13" ...... 等短语的文档。而第二条SQL,我们限制了最后一个词项的通配匹配个数为 2。
max_expansions 参数是分片级别的,也就是 max_expansions = 2 的话,每个分片最多匹配 2 个文档,如果有 3 个分片的话,最多返回 6 个匹配的文档。
一般来说 match_phrase_prefix API
可以实现比较粗糙的自动建议功能,但要实现自动建议的功能,可以使用 Suggest API
。
4.Multi match 多字段匹配
multi-match API
构建在 match
查询的基础上,可以允许在多个字段上执行相同的查询。
GET /order/_search
{
"query": {
"multi_match": {
"query": "oppo",
"fields": ["nam*", "order_no^3"]
}
}
}
best_fields | 默认的类型,会执行 match 查询并且将所有与查询匹配的文档作为结果返回,但是只使用评分最高的字段的评分来作为评分结果返回。 |
most_fields | 会执行 match 查询并且将所有与查询匹配的文档作为结果返回,并将所有匹配字段的评分加起来作为评分结果。 |
phrase | 在 fields 中的每个字段上均执行 match_phrase 查询,并将最佳匹配字段的评分作为结果返回。 |
phrase_prefix | 在 fields 中的字段上均执行 match_phrase_prefix 查询,并将最佳匹配字段的评分作为结果返回。 |
cross_fields | 它将所有字段当成一个大字段,并在每个字段中查找每个词。例如当需要查询英文人名的时候,可以将 first_name 和 last_name 两个字段组合起来当作 full_name 来查询。 |
bool_prefix | 在每个字段上创建一个 match_bool_prefix 查询,并且合并每个字段的评分作为评分结果。 |
上述的这几种类型,其实就是设置算分的方式和匹配文档的方式不一样,可以使用 type
字段来指定这些类型。
For Example:
GET /order/_search
{
"query": {
"multi_match": {
"query": "oppo",
"fields": ["name", "order_no"],
"type": "best_fields",
"tie_breaker": 0.3
}
}
}
该SQL将会在 order
索引中查找 "name" 字段包含 "oppo" 的文档或者在 "order_no" 字段中包含 "oppo" 的文档。
默认文档的相关性算分由得分最高的字段来决定的,但当指定 tie_breaker
的时候,算分结果将会由以下算法来决定:
- 令算分最高的字段的得分为 s1
- 令其他匹配的字段的算分 * tie_breaker 的和为 s2
- 最终算分为:s1 + s2
tie_breaker
的取值范围为:[0.0, 1.0]。当其为 0.0 的时候,按照上述公式来计算,表示使用最佳匹配字段的得分作为相关性算分。当其为 1.0 的时候,表示所有字段的得分同等重要。当其在 0.0 到 1.0 之间的时候,代表其他字段的得分也需要参与到总得分的计算当中去。通俗来说就是其他字段可以使用 "tie_breaker" 来进行“维权” 。
5.Match boolean prefix 布尔前缀匹配
一个 match_bool_prefix
查询会对输入的内容进行分词,并根据分词后的结果构造一个boolean查询。除最后一个Term外的每个Term都用于Term查询。最后一个Term用于前缀查询。
For Example:
GET order/_search
{
"query": {
"match_bool_prefix" : {
"name" : "quick brown f"
}
}
}
其中分析生成Term [quick,brown,f]类似于下面的bool查询。
GET order/_search
{
"query": {
"bool" : {
"should": [
{ "term": { "name": "quick" }},
{ "term": { "name": "brown" }},
{ "prefix": { "name": "f"}}
]
}
}
}
match_bool_prefix
和 match_phrase_prefix
一个很重要的不同在于 match_phrase_prefix
将输入的内容作为一个短语匹配,但是 match_bool_prefix
可以在任何位置匹配它的Terms。举个例子,match_bool_prefix
可以匹配一个包含 "quick brown fox" 的字段,也可以匹配 "brown fox quick",甚至可以匹配一个包含 "quick" 的字段,或者匹配一个任意位置以 "f" 开始的Term。
match_bool_prefix
默认使用的分词器是查询字段的分词器,也可以额外指定分词器。
GET order/_search
{
"query": {
"match_bool_prefix": {
"name": {
"query": "quick brown f",
"analyzer": "keyword"
}
}
}
}
match_bool_prefix
查询支持 minimum_should_match
和 operator
参数,这些参数的作用与match查询中描述的一样,并应用于构建的bool查询。构建的bool查询中的子句数量通常是通过对查询文本进行分析产生的术语数量。简单来说,match_bool_prefix
查询是一种结合了布尔查询和前缀查询的查询类型。minimum_should_match
参数指定了bool查询中至少应该匹配的子句数量,而 operator
参数指定了子句之间的逻辑操作符(AND或OR)。
[fuzziness、prefix_length、max_expansions、fuzzy_transpositions,fuzzy_rewrite]参数可以应用于为除最后一个术语之外的所有术语构建的子查询,但对于最后一个术语构建的前缀查询没有任何影响。简而言之,这些参数用于控制模糊匹配的行为。fuzziness
参数指定了模糊匹配的容错度,prefix_length
指定了前缀匹配的长度,max_expansions
指定了允许的最大扩展数,fuzzy_transpositions
指定了是否允许交换相邻字符进行匹配,而fuzzy_rewrite
参数指定了模糊匹配的重写方法。
6.Match all & none
最常用的查询,给所有的文档都打1.0分。
GET order/_search
{
"query": {
"match_all": {}
}
}
_score
可以通过 boost
参数修改。
GET order/_search
{
"query": {
"match_all": { "boost" : 1.2 }
}
}
还有一个和 match_all
相反的API,一条记录也不返回。
GET order/_search
{
"query": {
"match_none": {}
}
}