查询很少是对一个字段做 match 查询,通常都是一个 query 查询多个字段,比如一个 doc 有 title、content、pagetag 等文本字段,要在这些字段查询含多个 term 的 query,就要对它们的相关度评分做合理的合并。这被称为多词(multiword)、多字段(multifield)查询。

如果一个 query 可以结构化,如哪些词是 title,哪些词是 author,那么就可以直接在相关字段中查询,使用 bool 查询即可解决问题,bool 查询是“匹配越多越好”,如搜“War and Peace Leo Tolstoy”,查询语句如下:

  1. GET /_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. { "match": { "title": "War and Peace" }},
  7. { "match": { "author": "Leo Tolstoy" }}
  8. ]
  9. }
  10. }
  11. }

还可以对不同的字段加不同的 boost 权重。

以上被称为多重查询字符串,也可算是结构化查询,不过现实中通常是一个 query 在多个字段中查询,即单一查询字符串。毕竟对 query 做结构化需要些 nlp 技术和额外的人力成本,且比起单一查询字符串的效果提升也有限,所以若不是对召回效果有更高追求,还是不要轻举妄动,就好好做一个 query 在多个字段的查询吧。

一个 query 在多个字段中的查询,有三种策略:best_fields、most_fields、cross_fields。

介绍这三种策略之前,先铺垫下布尔查询和 dis_max 查询。

1. bool 查询

一个 query 在多个字段中的查询,同样可使用 bool 查询。

  1. {
  2. "query": {
  3. "bool": {
  4. "should": [
  5. { "match": { "title": "Brown fox" }},
  6. { "match": { "body": "Brown fox" }}
  7. ]
  8. }
  9. }
  10. }

不过由于 bool 查询评分公式的问题,效果不太好,比如一个文档 title 和 body 都包含 brown,不包含 fox,另一个文档在 body 字段包含了 brown 和 fox,显然后者更符合搜索意图,但 bool 查询的评分前者高,为了理解导致这样的原因,需要看下 bool 查询是如何计算评分的:

  • 它会执行 should 语句中的两个查询。

  • 加和两个查询的评分。

  • 乘以匹配字段的总数(这里不知是否理解正确,存疑,待验证)。

  • 除以所有语句总数(这里为:2)。

注意这里的“乘以匹配语句的总数”是关键,这会导致匹配字段越多,分值越大。(后面的 most_fields 也是使用这个计算,才使得匹配字段数越多,分值越大)

解决方案是,使用最佳匹配字段的分值作为整个查询的整体分值,让包含 query 两个单词的字段有更高的权重,而不是在不同的字段中重复出现的相同单词。dis_max 查询应运而生。

2. dis_max

dis_max 查询就是返回匹配了 query 的文档,分值是最佳匹配字段产生的分值。加上 tie_breaker 可得出很好的搜索效果。

  1. {
  2. "query": {
  3. "dis_max": {
  4. "queries": [
  5. { "match": { "title": "Quick pets" }},
  6. { "match": { "body": "Quick pets" }}
  7. ],
  8. "tie_breaker": 0.3
  9. }
  10. }
  11. }

3. best_fields

multi_match 查询提供了一个简便的方法对多个字段执行相同的查询。默认情况下,该查询以 best_fields 类型执行,它会为每个字段生成一个 match 查询,然后将这些查询包含在一个 dis_max 查询中。

例如:

  1. GET /_search
  2. {
  3. "query": {
  4. "multi_match" : {
  5. "query": "brown fox",
  6. "type": "best_fields",
  7. "fields": [ "subject", "message" ],
  8. "tie_breaker": 0.3
  9. }
  10. }
  11. }

执行时就变成了:

  1. GET /_search
  2. {
  3. "query": {
  4. "dis_max": {
  5. "queries": [
  6. { "match": { "subject": "brown fox" }},
  7. { "match": { "message": "brown fox" }}
  8. ],
  9. "tie_breaker": 0.3
  10. }
  11. }
  12. }

可通过 caret 语法(^) 对个别字段加权,如:

  1. {
  2. "multi_match": {
  3. "query": "Quick brown fox",
  4. "fields": [ "title", "chapter_title^2" ]
  5. }
  6. }

best_fields 和 most_fields 都是以字段为中心的查询,参数 operator 和 minimum_should_match 也是针对每个字段生效的,至少有一个字段满足要求,才会通过筛选并进入下一步计分,计分时也只有符合要求的字段才会参与计分。

operator 默认为 or,如果设置为 and,那么字段必须匹配所有 query 分词。当 operator 设为默认值 or 时,minimum_should_match 才会生效,设置每个字段应匹配分词数。

所以有些 query 信息是分布在多个字段上的,这时就不适合设置 operator 为 and,会减少召回量。如果确认 query 信息一定完全在某个字段上,则可设为 and。

为与 cross_fields 做对比,这里举个实际应用的例子。

搜索词为“苹果8plus国行”,文档有三个字段:cateName、title、content,其中 cateName 和 content 用 ik_smart 分词,title 用 ik_max_word 分词(不同字段的分词方法差异会在 cross_fields 中有所体现)。

看下 best_fields 查询的实际执行。

ES 语句:

  1. curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d'
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {
  6. "bool": {
  7. "should": [{
  8. "multi_match": {
  9. "query": "苹果8plus国行",
  10. "fields": ["cate_name^1.0", "content^1.0", "title^1.0"],
  11. "type": "best_fields",
  12. "operator": "AND",
  13. "tie_breaker": 0.3
  14. }
  15. }]
  16. }
  17. }
  18. }
  19. }
  20. }
  21. '

返回解释:

(
(+cate_name:苹果8 +cate_name:plus +cate_name:国行) |
(+content:苹果8 +content:plus +content:国行) |
(+title:苹果8 +title:苹果 +title:8plus +title:8 +title:plus +title:国 +title:行)
)~0.3

明显的以字段为中心的查询。

tips:字段名可以通过通配符指定,如:

  1. {
  2. "multi_match": {
  3. "query": "Quick brown fox",
  4. "fields": "*_title"
  5. }
  6. }

4. most_fields

有时为了尽可能多地匹配文档,会将同一文本的不同形式索引到多个字段。

ES语句(注意不要加 operator 或 minimum_should_match,不然就跟 best_fields 一样了):

  1. curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d'
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {
  6. "bool": {
  7. "should": [{
  8. "multi_match": {
  9. "query": "苹果8plus国行",
  10. "fields": ["cate_name^1.0", "content^1.0", "title^1.0"],
  11. "type": "most_fields"
  12. }
  13. }]
  14. }
  15. }
  16. }
  17. }
  18. }
  19. '

返回解释:

(
(cate_name:苹果8 cate_name:plus cate_name:国 cate_name:行) |
(content:苹果8 content:plus content:国 content:行) |
(title:苹果8 title:苹果 title:8plus title:8 title:plus title:国 title:行)
)~1.0

根据文档,most_fields 查询是用 bool 查询将两个字段语句包在里面,而不是像 best_fields 一样用 dis_max。(不知这个怎么验证,在自己的 ES 里试了下,看 explain 日志,没看出跟 best_fields 时 tie_breaker=1 有什么差别)

5. cross_fields

ES语句:

  1. curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d'
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {
  6. "bool": {
  7. "should": [{
  8. "multi_match": {
  9. "query": "苹果8plus国行",
  10. "fields": ["cate_name^1.0", "content^1.0", "title^1.0"],
  11. "type": "cross_fields",
  12. "operator": "AND",
  13. "tie_breaker": 0.3
  14. }
  15. }]
  16. }
  17. }
  18. }
  19. }
  20. }
  21. '

返回解释:

(
(+blended(terms:[cate_name:苹果8, content:苹果8]) +
blended(terms:[cate_name:plus, content:plus]) +
blended(terms:[cate_name:国行])) |
(+title:苹果8 +title:苹果 +title:8plus +title:8 +title:plus +title:国 +title:行)
)~0.3

这里 title 要和 cate_name、content 分开计算的原因,是因为两部分的分词方法不同,term 也不同。

根据《Elasticsearch: 权威指南》,关于 苹果8 的 IDF,会在 cate_name 和 content 中取最小值作为两个字段的 IDF。(待验证)

参考资料

  • Elasticsearch: 权威指南:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_best_fields.html
  • Elasticsearch Reference:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html

Elasticsearch 多字段搜索的更多相关文章

  1. elasticsearch多字段搜索

    https://blog.csdn.net/Ricky110/article/details/78888711 多字段搜索多字符串查询boost 参数 “最佳” 值,较为简单的方式就是不断试错,比较合 ...

  2. Elasticsearch 全字段搜索_all,query_string查询,不进行分词

    最近在使用ELasitcsearch的时候,需要用到关键字搜索,因为是全字段搜索,就需要使用_all字段的query_string进行搜索. 但是在使用的时候,遇到问题了.我们的业务并不需要分词,我在 ...

  3. [Elasticsearch] 多字段搜索 (五) - 以字段为中心的查询

    以字段为中心的查询(Field-centric Queries) 上述提到的三个问题都来源于most_fields是以字段为中心(Field-centric),而不是以词条为中心(Term-centr ...

  4. [Elasticsearch] 多字段搜索 (一) - 多个及单个查询字符串

    多字段搜索(Multifield Search) 本文翻译自官方指南的Multifield Search一章. 查询很少是只拥有一个match查询子句的查询.我们经常需要对一个或者多个字段使用相同或者 ...

  5. [Elasticsearch] 多字段搜索 (三) - multi_match查询和多数字段 <译>

    multi_match查询 multi_match查询提供了一个简便的方法用来对多个字段执行相同的查询. NOTE 存在几种类型的multi_match查询,其中的3种正好和在“了解你的数据”一节中提 ...

  6. [Elasticsearch] 多字段搜索 (六) - 自定义_all字段,跨域查询及精确值字段

    自定义_all字段 在元数据:_all字段中,我们解释了特殊的_all字段会将其它所有字段中的值作为一个大字符串进行索引.尽管将所有字段的值作为一个字段进行索引并不是非常灵活.如果有一个自定义的_al ...

  7. [Elasticsearch] 多字段搜索 (三) - multi_match查询和多数字段

    multi_match查询 multi_match查询提供了一个简便的方法用来对多个字段执行相同的查询. NOTE 存在几种类型的multi_match查询,其中的3种正好和在"了解你的数据 ...

  8. [Elasticsearch] 多字段搜索 (二) - 最佳字段查询及其调优

    最佳字段(Best Fields) 假设我们有一个让用户搜索博客文章的网站,就像这两份文档一样: PUT /my_index/my_type/1 { "title": " ...

  9. [Elasticsearch] 多字段搜索 (二) - 最佳字段查询及其调优(转)

    最佳字段(Best Fields) 假设我们有一个让用户搜索博客文章的网站,就像这两份文档一样: PUT /my_index/my_type/1 { "title": " ...

随机推荐

  1. spring boot集成shrio用于权限控制

    下面是一个简单的springBoot集成shrio的项目,技术是:spring boot+idea+gradle+shrio+mybatis 1:首先在build.gradle中导入依赖 builds ...

  2. Linux学习网站推荐

    最近想重新拾起Linux,发现了实验楼这个网站:https://www.shiyanlou.com/,可以通过这个网站学习Linux以及其他一些知识,可以直接学习直接动手操作,比较方便.

  3. LeetCode--53 最大连续子序列(总结)

    # 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. # 示例:# 输入: [-2,1,-3,4,-1,2,1,-5,4],# 输出: 6# 解释 ...

  4. C#:文件、byte[]、Stream相互转换

    一.byte[] 和 Stream /// <summary> /// byte[]转换成Stream /// </summary> /// <param name=&q ...

  5. c#及js实现将金融变成3位一逗号

    1.c#用string.format ToString("#,###.00") 2.js方法 转自http://www.cnblogs.com/cssfirefly/p/35820 ...

  6. XML—代码—DOM4J解析

    什么是xml: 众所周知,xml常用语数据存储和传输,文件后缀为 .xml: 它是可扩展标记语言(Extensible Markup Language,简称XML),是一种标记语言. 如何定义这些标记 ...

  7. mysql_connect

    in this passage, we slove the problem about Mysql_connect. first, let' see an example: resource mysq ...

  8. linux常用命令:route 命令

    Linux系统的route 命令用于显示和操作IP路由表(show / manipulate the IP routing table).要实现两个不同的子网之间的通信,需 要一台连接两个网络的路由器 ...

  9. redis安装及注意事项

    在linux中使用wget时,若报-bash: wget: command not found,则表明没有安装wget,需要安装,安装命令如下: yum -y install wget 安装完成即可以 ...

  10. 关于Context []startup failed due to previous errors

    文章转自:http://blog.sina.com.cn/s/blog_49b4a1f10100q93e.html 框架搭建好后,启动服务器出现如下的信息: log4j:WARN No appende ...