elasticsearch笔记_多字段搜索(六)

本文探讨了Elasticsearch中多字段搜索的最佳实践,包括最佳字段、多数字段及混合字段搜索策略,并详细介绍了如何利用dis_max查询和multi_match查询优化搜索结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多字符串对应多字段

多字符串是最简单的一种,例如,搜索标题是 War and Peace ,作者是Leo Tolstoy ,直接用bool连接两个match查询即可.(这里面War and Peace和Leo Tolstoy就是多字符串,title和author是多字段)

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":  "War and Peace" }},
        { "match": { "author": "Leo Tolstoy"   }}
      ]
    }
  }
}

单符串(多关键词)对应多字段

当输入单个字符串搜索时候,通常有下面三种需求:

1.最佳字段(best_fields) : 字符串整体的匹配度最高,而不是将字符创拆分成分词以后分词的匹配量最高 .(例如: “brown fox” ,词组比各自独立的单词更有意义。文档在 相同字段 中包含的词越多越好,评分也来自于 最匹配字段 。)

2.多数字段(most_fields) : 字符串在被拆分成分词以后,与每一个字段的匹配数量的和最大时被认为是匹配度最高.(这种情况多用于相关度微调技术 : 将相同的数据索引到不同的字段,它们各自具有独立的分析链.比如,一个字段被索引(动词)的是原词,另一个字段被索引(动词)的是这个词的变形词,口音,去掉过去式或复数形式等)

3.混合字段(cross_fields)
对多个字段的组合进行匹配.例如:
(1) Person: first_name 和 last_name (人:名和姓)
(2) Book: title 、 author 和 description (书:标题、作者、描述)
(3) Address: street 、 city 、 country 和 postcode (地址:街道、市、国家和邮政编码)

最佳字段(best_fields)

1 . 第一种搜索情况 : 输入”Brown fox”对title和body进行搜索.(注意:doc1里面title和body都只含有brown ; doc2里面的title不含任何关键字 , body里面含有Brown fox整体.)

//以下面两个文档为例:

{
    "title": "Quick brown rabbits",
    "body":  "Brown rabbits are commonly seen."
}

{
    "title": "Keeping pets healthy",
    "body":  "My quick brown fox eats rabbits on a regular basis."
}

//输入"Brown fox"对title和body进行搜索.

{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

分析 : 根据肉眼观察,”Brown fox”明显和文档2更加匹配 , 但是搜索结果却是文档1的评分更高 . 这与bool的评分方式有关 . bool的评分方式是:(1)将每一个should的查询结果评分进行相加. (2)乘以匹配语句的总数 . (3)除以全部语句的总数.

很明显,上面的评分结果并不是我们想要的 . 最大化查询就是为了解决上面的搜索情况的.

分离最大化查询(Disjunction Max Query)

分离最大化查询(dis_max):将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回 .

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

2.第二种搜索情况 : 输入”Quick pets”对title和body进行搜索 . (注意 : 两个文档都包含词 quick ,但是只有文档 2 包含词 pets)

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ]
        }
    }
}

此时会发现两个文档的用dis_max查询的评分是相同的 . 也是明显不符合我们想要的结果,这种情况就要用到tie_breaker参数了.

tie_breaker参数
{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}

分析过程 :
1 . 获得最佳匹配语句的评分 _score 。
2 . 将其他匹配语句的评分结果与 tie_breaker 相乘。
3 . 对以上评分求和并规范化。

注意 :tie_breaker 可以是 0 到 1 之间的浮点数,其中 0 代表使用 dis_max 最佳匹配语句的普通逻辑, 1 表示所有匹配语句同等重要。最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max 最佳匹配性质的根本。

multi_match查询

multi_match是为了简化多个match的情况 . 例如 :默认情况下,查询的类型是 best_fields(最佳字段) , 而dis_max是最佳字段最常用的查询方式 , dis_max的底层实现是:

{
  "dis_max": {
    "queries":  [
      {
        "match": {
          "title": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
      {
        "match": {
          "body": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
    ],
    "tie_breaker": 0.3
  }
}

multi_match就是为了简化类似这样的查询:

{
    "multi_match": {
        "query":                "Quick brown fox",
        "type":                 "best_fields",     //best_fields 类型是默认值,可以不指定。
        "fields":               [ "title", "body" ],
        "tie_breaker":          0.3,
        "minimum_should_match": "30%" 
    }
}

multi_match还支持字段模糊查询 .

//匹配 book_title 、 chapter_title 和 section_title (书名、章名、节名)这三个字段:

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": "*_title"
    }
}

提升单个字段的权重:

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": [ "*_title", "chapter_title^2" ] 
    }
}

多数字段(most_fields)

提高全文相关性精度的常用方式是为同一文本建立多种方式的索引, 每种方式都提供了一个不同的相关度信号 signal 。主字段会包括最广匹配(broadest-matching)形式的词去尽可能的匹配更多的文档。举个例子,我们可以进行以下操作:

1.使用词干提取来索引 jumps 、 jumping 和 jumped 样的词,将 jump 作为它们的词根形式。这样即使用户搜索 jumped ,也还是能找到包含 jumping 的匹配的文档。
将同义词包括其中,如 jump 、 leap 和 hop 。

2.移除变音或口音词:如 ésta 、 está 和 esta 都会以无变音形式 esta 来索引。
尽管如此,如果我们有两个文档,其中一个包含词 jumped ,另一个包含词 jumping ,用户很可能期望前者能排的更高,因为它正好与输入的搜索条件一致。

DELETE /my_index

PUT /my_index
{
    "settings": { "number_of_shards": 1 }, 
    "mappings": {
        "my_type": {
            "properties": {
                "title": { 
                    "type":     "string",
                    "analyzer": "english",
                    "fields": {
                        "std":   { 
                            "type":     "string",
                            "analyzer": "standard"
                        }
                    }
                }
            }
        }
    }
}

//添加两个新的文档.

PUT /my_index/my_type/1
{ "title": "My rabbit jumps" }

PUT /my_index/my_type/2
{ "title": "Jumping jack rabbits" }

查询title字段,结果是两个文档的评分相同,因为title用的是english分析器.如果只是查询 title.std 字段,那么只有文档 2 是匹配的。

GET /my_index/_search
{
   "query": {
        "match": {
            "title": "jumping rabbits"
        }
    }
}

如果同时查询两个字段,然后使用 bool 查询将评分结果 合并 ,那么两个文档都是匹配的( title 字段的作用),而且文档 2 的相关度评分更高( title.std 字段的作用):


GET /my_index/_search
{
   "query": {
        "multi_match": {
            "query":  "jumping rabbits",
            "type":   "most_fields", 
            "fields": [ "title", "title.std" ]
        }
    }
}

每个字段对于最终评分的贡献可以通过自定义值 boost 来控制。比如,使 title 字段更为重要,这样同时也降低了其他信号字段的作用:

GET /my_index/_search
{
   "query": {
        "multi_match": {
            "query":       "jumping rabbits",
            "type":        "most_fields",
            "fields":      [ "title^10", "title.std" ] 
        }
    }
}

混合字段(cross_fields)

现在想要查询 “Poland Street W1V” 这个地址 , 地址的索引(名词)是这样建立的:

{
    "street":   "5 Poland Street",
    "city":     "London",
    "country":  "United Kingdom",
    "postcode": "W1V 3DG"
}

最简单的方式是这样的:

{
  "query": {
    "bool": {
      "should": [
        { "match": { "street":    "Poland Street W1V" }},
        { "match": { "city":      "Poland Street W1V" }},
        { "match": { "country":   "Poland Street W1V" }},
        { "match": { "postcode":  "Poland Street W1V" }}
      ]
    }
  }
}

另一种形式:
{
  "query": {
    "multi_match": {
      "query":       "Poland Street W1V",
      "type":        "most_fields",    //注意,不是cross_fields
      "fields":      [ "street", "city", "country", "postcode" ]
    }
  }
}

注意:most_fields 这种方式搜索也存在某些问题.
问题1. 它是为多数字段匹配 任意 词设计的,而不是在 所有字段 中找到最匹配的。
问题2. 它不能使用 operator或and 操作符 或 minimum_should_match 参数来降低次相关结果造成的长尾效应。
问题3. 词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。

通过 validate-query API 查看查询的解释 :

GET /_validate/query?explain
{
  "query": {
    "multi_match": {
      "query":   "Poland Street W1V",
      "type":    "most_fields",
      "fields":  [ "street", "city", "country", "postcode" ]
    }
  }
}

生成 explanation 解释:

(street:poland   street:street   street:w1v)
(city:poland     city:street     city:w1v)
(country:poland  country:street  country:w1v)
(postcode:poland postcode:street postcode:w1v)

注意 : 可以发现, 两个 字段都与 poland 匹配的文档要比一个字段同时匹配 poland 与 street 文档的评分高。(这就是问题1)

{
    "query": {
        "multi_match": {
            "query":       "Poland Street W1V",
            "type":        "most_fields",
            "operator":    "and", 
            "fields":      [ "street", "city", "country", "postcode" ]
        }
    }
}

注意 : 使用 and 操作符要求所有词都必须存在于 相同字段 ,这显然是不对的!可能就不存在能与这个查询匹配的文档。(这就是问题2)

{
    "query": {
        "multi_match": {
            "query":       "Peter Smith",
            "type":        "most_fields",
            "fields":      [ "*_name" ]
        }
    }
}

注意 : 用字段 first_name 和 last_name 查询 “Peter Smith” 时, Peter 是个平常的名 Smith 也是平常的姓,这两者都具有较低的 IDF 值。但当索引中有另外一个人的名字是 “Smith Williams” 时,姓 Smith 就会非常的不平常,以致它有一个较高的 IDF 值!

这个查询可能会在结果中将 “Smith Williams” 置于 “Peter Smith” 之上,尽管事实上是第二个人比第一个人更为匹配。(这就是问题3)

解决方案

1.创建一个full_name , 将搜索转化为搜索full_name , 缺点就是存储冗余数据.

{
    "first_name":  "Peter",
    "last_name":   "Smith",
    "full_name":   "Peter Smith"
}

2.自定义_all字段,利用copy_to来实现,copy_to 设置对multi-field无效。如果尝试这样配置映射,Elasticsearch 会抛异常。为什么呢?多字段只是以不同方式简单索引“主”字段;它们没有自己的数据源。也就是说没有可供 copy_to 到另一字段的数据源。

PUT /my_index
{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "last_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "full_name": {
                    "type":     "string"
                }
            }
        }
    }
}

3.cross_field词中心匹配 , 为了让 cross_fields 查询以最优方式工作,所有的字段都须使用相同的分析器, 具有相同分析器的字段会被分组在一起作为混合字段使用。

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", 
            "operator":    "and",
            "fields":      [ "first_name", "last_name" ]
        }
    }
}

搜索过程 : 它会同时在 first_name 和 last_name 两个字段中查找 smith 的 IDF ,然后用两者的最小值作为两个字段的 IDF 。结果实际上就是 smith 会被认为既是个平常的姓,也是平常的名。

为了让 cross_fields 查询以最优方式工作,所有的字段都须使用相同的分析器, 具有相同分析器的字段会被分组在一起作为混合字段使用。

cross_fields 查询与 自定义 _all 字段 相比,其中一个优势就是它可以在搜索时为单个字段提升权重。

GET /books/_search
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields",
            "fields":      [ "title^2", "description" ] 
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值