ElasticSearch学习随笔之高级检索

ElasticSearch

1、ElasticSearch学习随笔之基础介绍
2、ElasticSearch学习随笔之简单操作
3、ElasticSearch学习随笔之java api 操作
4、ElasticSearch学习随笔之SpringBoot Starter 操作
5、ElasticSearch学习随笔之嵌套操作
6、ElasticSearch学习随笔之分词算法
7、ElasticSearch学习随笔之高级检索
8、ELK技术栈介绍
9、Logstash部署与使用
10、ElasticSearch 7.x 版本使用 BulkProcessor 实现批量添加数据
11、ElasticSearch 8.x 弃用了 High Level REST Client,移除了 Java Transport Client,推荐使用 Elasticsearch Java API
12、ElasticSearch 8.x 使用 snapshot(快照)进行数据迁移
13、ElasticSearch 8.x 版本如何使用 SearchRequestBuilder 检索
14、ElasticSearch 8.x 使用 High Level Client 以 HTTPS 方式链接,SSL 证书、主机名验证器 各是什么,如何忽略
15、ElasticSearch 8.x 创建父子文档,用Join类型字段以及用has_child、has_parent 检索
16、ElasticSearch如何提高写入效率【面试题,面道既学到】

ElasticSearch,创始人 Shay Banon(谢巴农)
本文主要讲解ElasticSearch 高级搜索实战,来满足复杂的业务场景,还是用 Kibana 来操作。



前言

本文主要介绍 ES 的一下高级检索功能,80% 的业务场景,简单搜索就可以实现了,但是在一些复杂的业务场景中,我们必须使用一些高级的功能来满足,比如在一些与舆情监测项目或者智能推荐、猜你想搜这种复杂的功能,有时候需要搜索中需要提高相关度,有时候搜索需要减低相关度等。
Don’t bebe so much, show the codes…

让我们来准备一些数据来进行高级搜索测试,但是问题来了,这些数据真的是头疼,不好生成一些示例数据出来,这时候,chatGTP这是无敌了,简直太方便了。
在这里插入图片描述

一、Boosting(控制相关度)

Boosting 是控制相关度的一种手段,此操作最好是需要 boost 参数来控制评分权重,如下:

  • boost > 1时,打分的权重相对性提升
  • 0 < boost < 1时,打分权重相对性降低
  • boot < 0 时,负分

“positive” 查询用于匹配你感兴趣的文档,而 “negative” 查询匹配的文档会被降级,这就可以在不排除某些文档的情况下对文档进行检索,只是结果中存在相似度较低的文档,排在后面。
negative_boost 对 negative 部分 query 生效,在计算评分时,negative 部分 query 评分乘以 negative_boost 值。

GET electronics/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "desc": "手机"
        }
      },
      "negative": {
        "match": {
          "desc": "相机"
        }
      },
      "negative_boost": 0.2
    }
  },
  "from": 1,
  "size": 10
}

应用场景:希望检索的关键词的结果不是不出现,而是排在最后面。

二、Bool 布尔查询

2.1 复合查询

Bool 查询是一个或者多个子查询的组合体,包括 4 中子句,其中 2 种是影响评分的,两种不影响评分,具体如下:

  • must:相当于JAVA中的 && 操作符,必须匹配,支持评分。
  • should:相当于 JAVA 中的 || 操作符,选择性匹配,支持评分。
  • must_not:相当于JAVA中的 !,必须不匹配,不支持评分。
  • filter:简单的过滤,不支持评分。

注意面试中可能会被问到:

操作描述
Query会进行相关性算分,检索性能不高
Filter不会进行相关性算分,检索性能会更好

注意: 把多个子查询合并为一个复合查询时,比如 bool 查询,每个子查询计算的评分会被合并到相关性总评分中。

布尔查询语法也是相当灵活:
1、子查询的顺序随意;
2、也可以嵌套多个子查询;
3、没有 must 条件的情况下,should 条件至少满足一项。

比如下面这一条示例,检索了 electronics 索引中,产品名称(title)带 “苹果” 的,描述(desc)中带有 “手机” 或者 “笔记本” 的,品牌(brand)是 “Apple” 的,颜色过滤掉 “black” 的结果。

brand.keyword 这里, 因为我并没有定义 schema,但是我用了 term 来精确匹配的,所以需要用 (.keyword) 来把 brand 字段设置成关键词匹配。

POST electronics/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "苹果"
          }
        }
      ],
      "should": [
        {
          "match": {
            "desc": "手机"
          }
        },
        {
          "match": {
            "desc": "笔记本"
          }
        }
      ],
      "filter": {
        "term": {
          "brand.keyword": "Apple"
        }
      },
      "must_not": [
        {
          "term": {
            "color": "black"
          }
        }
      ]
    }
  }
}

2.2 包含不相等查询

在具体业务中,有的业务数据并不是一个简单的字符串或者是数值,而是一个多值的情况,这种情况下我们在检索时希望精确匹配到多值中的一项,可以用 match 来查询,语句如下:

POST electronics/_search
{
  "query": {
    "match": {
      "product_agency": "打电话"
    }
  },
  "from": 0,
  "size": 20
}

我们也可以换成 term 来精确匹配,需要加上 .keyword,语句如下:

POST electronics/_search
{
  "query": {
    "term": {
      "product_agency.keyword": "打电话"
    }
  },
  "from": 0,
  "size": 20
}

以上两种方式,match 和 term 都可以查询出 product_agency 是 “打电话” 的手机信息,不过 “谷歌” 的手机确实没用过,那打电话的功能应该是有的,我要查询出只能打电话的,这时候可以增加 count 字段来解决这个问题,语句如下:

当然,如果 must 改为 filter, 则效率更佳!

POST electronics/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "product_agency.keyword": {
              "value": "打电话"
            }
          }
        },
        {
          "term": {
            "product_agency_count": {
              "value": 1
            }
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 20
}

说实话,写完之后自我觉得这个例子没啥特别之处。

2.3 实现 should_not 查询

有没有发现 bool 查询里面 should_not 是没有的,我们可以通过 bool 嵌套多个子查询来实现,在 should 中嵌套 bool,再嵌套 must_not 来实现,语句如下:

POST electronics/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "product_agency.keyword": {
              "value": "打电话"
            }
          }
        }
      ],
      "should": [
        {
          "bool": {
            "must_not": [
              {
                "term": {
                  "price": {
                    "value": 699.99
                  }
                }
              }
            ]
          }
        }
      ],
      "minimum_should_match": 1
    }
  },
  "from": 0,
  "size": 20
}

注意: 这里需要加上 minimum_should_match 这个参数,最少匹配到一个 should 查询,这样才会生效。
说实话,这种只有在鸡肋业务中才会用到吧。

2.4 控制查询相关性算分

三、多字段查询

当值多字段查询操作在工作中很常见,大多数情况下就是用户数据一个关键词(通常是一个字符串),然后匹配到相关性最高的文档。

有三种这样的场景:

  • 最佳字段(Best Fields):字段直接相互竞争又相互关联,比如 title 和 content 字段,评分来自最匹配字段。
  • 多数字段(Most Fields):处理英文内容常见手段,在主字段(English Analyzer)抽取词干,加入同义词以匹配更多的文档;相同的文本,加入子字段(Standard Analyzer)以更加精准的匹配。
  • 混合字段(Cross Field):对于某些信息,比如 名称、地址、图书的信息等等,需要多个字段才能确定,单个字段只能作为整体的一部分,希望在这些字段中尽可能找到多的词。

3.1 最佳字段匹配(Best Fields)

在多个字段匹配时,用 bool 的 should 就可以实现,比如下面这条查询:

POST electronics/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "苹果"
          }
        },
        {
          "match": {
            "desc": "苹果"
          }
        }
      ]
    }
  }
}

但是这个查询并不会匹配到最好的结果,为什么?
should 的评分过程是这样的,匹配 should 语句中的多个子查询,加和多个子查询的评分,乘以匹配语句的总评分,除以语句的总数,显然这样的评分不够完美。
title 和 desc 两个字段属于竞争关系,所以不应该是评分简单加和,而是单个最佳字段的评分。

3.1.1 Dis Max Query

且看下面这条查询,可以通过 tie_breaker 来进行调整。
tie_breaker 介于 0 ~ 1 之间的浮点数,0 代表使用最佳匹配,1 代表所有语句同等重要。
1、取得最佳匹配的评分 _score。
2、将其他语句的评分与 tie_breaker(决胜局的意思) 进行相乘。
3、对所有评分求和并进行规范化。

看官网解释!!

POST electronics/_search
{
  "query": {
    "dis_max": {
      "tie_breaker": 0.7,
      "boost": 1.2,
      "queries": [
        {
          "match": {
            "title": "苹果"
          }
        },
        {
          "match": {
            "desc": "苹果"
          }
        }
      ]
    }
  }
}

3.1.2 Multi Match Query

另一种方式就是使用 multi_match 来检索,这种方式默认就是 best_fields 检索,所以可以不用指定 type 参数,tie_breaker 按照结果可调节。

POST electronics/_search
{
  "query": {
    "multi_match": {
      "query": "苹果",
      "fields": ["title", "desc"],
      "type": "best_fields",
      "tie_breaker": 0.2
    }
  }
}

3.2 多数字段匹配(Most Fields)

准备一些测试数据,首先来创建一个 index 并且定义 schema,加两条测试数据。

PUT /blogs
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "english",
        "fields": {
          "std": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      }
    }
  }
}
POST blogs/_bulk
{ "index": { "_id": 1 }}
{ "title": "My dog barks" }
{ "index": { "_id": 2 }}
{ "title": "I see a lot of barking dogs on the road " }
{ "index": { "_id": 3 }}
{ "title": "I have a dog named bark" }
{ "index": { "_id": 4 }}
{ "title": "I see barking dogs on the street, they are running and playing " }

用下面的检索语句来查询,发现查询出了所有的数据,结果与我们的预期不匹配

GET blogs/_search
{
  "query": {
    "match": {
      "title": "braking dogs"
    }
  }
}

下面的查询语句来查询,发现结果好了很多,用 title 字段来尽可能的匹配更多的文档,提升召回率,同时又用 title.std 将相关度更高的文档置顶

GET blogs/_search
{
  "query": {
    "multi_match": {
      "query": "braking dogs",
      "fields": ["title", "title.std"],
      "type": "most_fields"
    }
  }
}

我们可以更精确一点,每个字段对于最终评分可以通过自定义评分 boost 来控制。
比如 title 字段更重要,我们可以用 boost 来控制,同时也降低了其他信号字段的作用。

GET blogs/_search
{
  "query": {
    "multi_match": {
      "query": "braking dogs",
      "fields": ["title^10", "title.std"],
      "type": "most_fields"
    }
  }
}

3.3 跨字段匹配(Cross Fields)

创建一个 address 的 index,字段是 provice 和 city,利用 ik 分词器分词。

PUT /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": "邵阳"}
{ "index": { "_id": "5"} }
{"province": "甘肃","city": "武威"}
{ "index": { "_id": "6"} }
{"province": "甘肃","city": "玉门"}

先用 most_fields 的方式来查询 “甘肃酒泉”,但是结果不符合预期,甘肃玉门 和 甘肃武威 也出来了,查询不支持 operator 。

GET address/_search
{
  "query": {
    "multi_match": {
      "query": "甘肃酒泉",
      "fields": ["province", "city"],
      "type": "most_fields"
    }
  }
}

使用 cross_fields 来查询,支持 operator 操作。
查询结果符合我们的预期,只返回了 甘肃酒泉 的文档。

GET address/_search
{
  "query": {
    "multi_match": {
      "query": "甘肃酒泉",
      "fields": ["province","city"],
      "type": "cross_fields",
      "operator": "and"
    }
  }
}

还有另一种方式来解决,就是增加一个字段,用于检索完整的地址,比如:full_address 字段。

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"
        }
    }
}

这样我们就可以用 full_address 字段来检索 “甘肃酒泉” 这个关键词了,利用 match 匹配就可以了,语句如下:

GET /address/_search
{
  "query": {
    "match": {
      "full_address": {
        "query": "湖南常德",
        "operator": "and"
      }
    }
  }
}

3.4 Terms Set 检索

今天看到一个新的检索方式,官方公众号介绍,这里做一下补充。
注意 :ElasticSearch 6.1 版中引入的新功能。
在检索多值字段时,利用 terms 进行精确匹配,利用minimum_should_match_field 参数指定一个包含匹配数量的字段(product_count)。

GET electronics/_search
{
  "query": {
    "terms_set": {
      "product_agency.keyword":{
        "terms":["打电话","王者","看电影"],
        "minimum_should_match_field":"product_count"
      }
    }
  }
}

搜索结果

利用 minimum_should_match_script 检索,指定一个包含匹配数量的字段(product_count)的 70% 数量动态计算匹配。

GET electronics2/_search
{
  "query": {
    "terms_set": {
      "product_agency":{
        "terms":["打电话","办公","看大片"],
        "minimum_should_match_script":{
          "source":"doc['product_count'].value * 0.7"
        }
      }
    }
  }
}

检索结果

总结

ES 是目前全文检索排行榜 首位,而且不断升级,现在一到 8.x 版本了,基本上 solr 搜索引擎用的公司已经不多了,虽然搜索速度很快,但做不到实时,这就很尴尬了,再加上定制写一写什么关联查询,简直鸡肋了。
ES 提供了很多高级检索,能很好的满足一些复杂的业务场景,可以嵌套,这样就不用总是加字段这种很 low 的方式来解决一些业务问题了。
全文检索排行老大

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值