场景描述:
有这样一种场景,比如我们想搜索
title:Brown fox
body:Brown fox
文章索引中有两条数据,兔子和狐狸两条数据
PUT /blogs/_bulk
{"index": {"_id": 1}}
{"title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen."}
{"index": {"_id": 2}}
{"title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis."}
结果肯定是想要数据二,狐狸优先展示
但是,然后搜索的时候,会对搜素词 Brown fox 进行分词,导致数据一优先级更高
可以看下结果:
优先展示的是兔子,有 0.8 的算分,而狐狸只有 0.7 的算分
GET /blogs/_search
{
"query": {
"bool": {
"should": [
{"match": {
"title": "Brown fox"
}},
{"match": {
"body": "Brown fox"
}}
]
}
}
}
原因分析:
bool should的算法过程:
- 查询should语句中的两个查询
- 加和两个查询的评分
- 乘以匹配语句的总数
- 除以所有语句的总数
上述例子中,title和body属于竞争关系,不应该将分数简单叠加,而是应该找到单个最佳匹配的字段的评分。
解决方案
方式一 dis_max:
可以采用 dis max query
实例如下:可以看到此时达到了我们想要的结果
GET /blogs/_search
{
"query": {
"dis_max": {
"queries": [
{
"match":{
"title": "Brown fox"
}
},
{
"match": {
"body": "Brown fox"
}
}
]
}
}
}
这里简单解释一下这两种命令产生的原因:
类似 MySQL 可以使用 explain 关键字分析指令
先分析 bool should
如下看兔子这条数据,总算分是两个字段的算分之和
GET /blogs/_search
{
"explain": true,
"query": {
"bool": {
"should": [
{"match": {
"title": "Brown fox"
}},
{"match": {
"body": "Brown fox"
}}
]
}
}
}
再来看 dis_max
如下同样看兔子的这条数据,可以看到,此时这条数据的总算分是其中一个字段的最大值
GET /blogs/_search
{
"explain": true,
"query": {
"dis_max": {
"queries": [
{
"match":{
"title": "Brown fox"
}
},
{
"match": {
"body": "Brown fox"
}
}
]
}
}
}
dis_max 还可以使用 tie_breaker 控制非最大值字段的算分
tier breaker是一个介于0-1之间的浮点数。0代表使用最佳匹配;1代表所有语句同等重要。
- 获得最佳匹配语句的评分_score 。
- 将其他匹配语句的评分与tie_breaker相乘
- 对以上评分求和并规范化
最终得分=最佳匹配字段+其他匹配字段*tie_breaker
此时可以看到
兔子这条数据的算分 0.714258 = 0.6931471 + 0.21110919 * 0.1
0.1 就是 tier breaker 的数值
方式二 multi_mahch :
还记得之前篇章里面学到的 multi_match 多字段查询么
看到下面结果中的算分是不是有点似曾相识,
没错,multi_mahch 默认的查询方式就是两字段取最大值的方式
算分方式和上面一致,可以自行使用 explain 进行尝试
GET /blogs/_search
{
"query": {
"multi_match": {
"query": "Brown fox",
"fields": ["title","body"]
}
}
}
同样的 multi_mahch 也可以使用 tie_breaker 控制最佳匹配之外字段的算分
GET /blogs/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "Brown fox",
"fields": ["title","body"],
"tie_breaker": 0.1
}
}
}
multi_mahch 有三种方式
best_fields
这种方式就是默认的方式,就不再演示了
GET /blogs/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "Brown fox",
"fields": ["title","body"]
}
}
}
most_fields
这种方式就是上面 bool should 求和的方式
GET /blogs/_search
{
"query": {
"multi_match": {
"type": "most_fields",
"query": "Brown fox",
"fields": ["title","body"]
}
}
}
cross_fields
跨字段查询
搜索内容在多个字段中都显示,类似 bool+dis_max 组合
PUT /address/_bulk
{ "index": { "_id": "1"} }
{"province": "湖南","city": "长沙"}
{ "index": { "_id": "2"} }
{"province": "湖南","city": "常德"}
{ "index": { "_id": "3"} }
{"province": "广东","city": "广州"}
{ "index": { "_id": "4"} }
{"province": "湖南","city": "邵阳"}
# 可以使用cross_fields,支持operator
#与copy_to相比,其中一个优势就是它可以在搜索时为单个字段提升权重。
GET /address/_search
{
"query": {
"multi_match": {
"query": "湖南常德",
"type": "cross_fields",
"operator": "and",
"fields": ["province","city"]
}
}
}
这里跨字段还有另一种方式:
可以用copy...to 解决,但是需要额外的存储空间
DELETE /address
# copy_to参数允许将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询
PUT /address
{
"mappings" : {
"properties" : {
"province" : {
"type" : "keyword",
"copy_to": "full_address"
},
"city" : {
"type" : "text",
"copy_to": "full_address"
}
}
},
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
PUT /address/_bulk
{ "index": { "_id": "1"} }
{"province": "湖南","city": "长沙"}
{ "index": { "_id": "2"} }
{"province": "湖南","city": "常德"}
{ "index": { "_id": "3"} }
{"province": "广东","city": "广州"}
{ "index": { "_id": "4"} }
{"province": "湖南","city": "邵阳"}
GET /address/_search
{
"query": {
"match": {
"full_address": {
"query": "湖南常德",
"operator": "and"
}
}
}
}