多字符串查询
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }},
{ "bool": {
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}
4个match查询,为什么后两个使用bool包裹?
同一层查询每条语句具有相同的权重!!!
语句的优先级
boost
单字符串查询
用户期望将所有的搜索项堆积到单个字段中。
1、最佳字段
例如我们搜索“es reindex”,对于title和body这样的两个字段,“es reindex”同时出现在一个字段时的文档评分更高
2、多数字段
为了对相关度进行微调,常用的一个技术就是将相同的数据索引到不同的字段,使他们具有独立的分析链。
主字段可能包括它们的词源、同义词以及变音词或口音词等,用来匹配尽可能多的文档。
相同的文本被索引到其他字段,提供更精确的匹配。一个字段可以包括未经词干提取的原词;另一个字段包括口音,还有一个字段提供词语相似性。
其他字段是作为匹配每个文档时提高相关度评分的信号。
3、混合字段
对于某些实体,需要在多个字段中确认其信息,单个字段都只能作为整体的一部分:
Person: first_name、last_name
如同在Persion这个大字段中搜索 first_name和last_name这两个字段
最佳字段
以两个文档为例
PUT /my_index/my_type/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /my_index/my_type/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
bool查询,想要匹配Brown fox
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
我们想要的结果是文档2,但是文档1的评分更高
bool如何计算评分:
1、执行should语句中的两个查询。
2、两个查询的评分相加
3、乘以匹配语句的总数
4、除以所有语句总数(这里为2,指的应该两个字段???)
文档1中两个字段都包含brown,所以两个match语句都有一个评分。
文档2中body字段包含两个词,获得较高分,但title字段没有包含任何词,平均一下文档2的整体分就比文档1低。
可以使用dis_max分离最大化查询Disjunction Max Query,大致意思是,将任何与任一查询匹配的文档作为结果返回,但只将最匹配的评分作为查询的评分结果返回:
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
最佳字段查询调优
dis_max查询会单个匹配最佳字段,而忽略其他的匹配。(单个最佳匹配语句的评分作为整体评分)
可以通过指定tie_breaker字段将其他语句的评分也考虑其中。
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
结果如下:
{
"hits": [
{
"_id": "2",
"_score": 0.14757764, (1)
"_source": {
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_id": "1",
"_score": 0.124275915, (1)
"_source": {
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
}
]
}
不带有tie_breaker参数时,评分1等于评分2
tie_breaker的评分方式:
1、获得最佳匹配语句的评分
2、其他语句的评分结果乘以tie_breaker
3、上述相加即为最后评分
tie_breaker的合理值应该处于0.1-0.4之间
multi_match多匹配查询
在多个字段上反复执行相同的查询。
multi_match多匹配查询的类型有多种,其中三种与前文的最佳字段(best_fields)、多数字段(most_fields)、跨字段相匹配(cross_fields)
默认情况查询的类型是best_fields,例如:
{
"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", (1)
"fields": [ "title", "body" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%" (2)
}
}
minimum_should_match、operator 这样的参数会被传递到生成的match查询中
匹配的字段可以使用模糊匹配的方式给出,例如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
可以使用^
为单个字段提升权重
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ] (1)
}
}
多数字段
召回率:返回所有的相关文档
精确率:不返回无关文档
全文搜索的两个名词,目的是在结果的第一页为用户呈现最为相关的文档
多字段映射,首先要做的事情就是对我们的字段索引两次,一次使用词干模式,一次使用非词干模式
PUT /my_index
{
"settings": { "number_of_shards": 1 }, (1)
"mappings": {
"my_type": {
"properties": {
"title": { (2)
"type": "string",
"analyzer": "english",
"fields": {
"std": { (3)
"type": "string",
"analyzer": "standard"
}
}
}
}
}
}
}
简单的查询,两个文档的评分相同;只查询title.std那么只有文档2是匹配的
GET /my_index/_search
{
"query": {
"match": {
"title": "jumping rabbits"
}
}
}
如果同时查询两个字段,然后使用bool查询将结果合并,那么两个文档都是匹配的,文档2的分更高
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields", (1)
"fields": [ "title", "title.std" ]
}
}
}
用广度匹配字段title
匹配尽可能多的文档,提升召回率
同时又使用title.std
作为信号将相关度更高的文档置于结果顶部
跨字段实体搜索
多字符串查询中,我们为每个字段使用不同的字符串,
而跨字段查询是使用单个字符串在多个字段中进行搜索。
例如我们搜索Poland Street W1V
这个地址,它需要匹配多个字段
假如使用motst_fields会出现如下问题:
1、它是为多数字段匹配任意词设计的,而不是在所有字段中找到最匹配的
2、它不能使用operator或minimum_should_match参数来降低相关结果造成的长尾效应
3、词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。
字段中心式查询
most_fields(best_fields)出现的问题都是因为它是字段中心式查询,而不是词中心式的。
词中心式:当真正感兴趣的事匹配词的时候,它为我们查找的是最匹配的字段
问题1:在多个字段中匹配相同的词
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文档的评分高(可能是顺序,得出这样的结论)
问题2:剪掉长尾
在精度匹配中,我们可以使用operator和minimum_should_match参数来消除结果中几乎不相关的长尾,但是对于best_fields和most_fields中,这些参数会在match查询生成时被传入,例如操作符and,就会要求所有词必须存在与相同字段中,这显然是不对的
问题3:词频
{
"query": {
"multi_match": {
"query": "Peter Smith",
"type": "most_fields",
"fields": [ "*_name" ]
}
}
}
可能在结果中将“Smith Williams” 置于 “Peter Smith” 之上。
简单来说就是smith在名字段中具有较高的IDF,他会削弱“Peter”作为名和“smith”作为姓时低IDF的所起作用。
解决方案
存在这些问题仅仅是因为我们在处理着多个字段,如果将这些字段组合成单个字段,问题就会消失。
{
"first_name": "Peter",
"last_name": "Smith",
"full_name": "Peter Smith"
}
这样只要查询full_name刚才的问题就会消失,但是数据却会冗余。取而代之的是另外两种方案,一个是在索引时,另一个是在搜索时
自定义_all字段
_all字段的索引方式是将所有其他字段的值作为一个大字符串索引的。然而这么做并不灵活,为了灵活,我们可以给人名添加一个自定义_all字段,也为地址添加另一个_all字段
可以使用copy_to参数来实现这个功能
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name" (1)
},
"last_name": {
"type": "string",
"copy_to": "full_name" (1)
},
"full_name": {
"type": "string"
}
}
}
}
}
first_name和last_name字段的值会被复制到full_name中
copy_to设置对multi_field无效。
多字段只是以不同方式简单索引“主”字段,他们没有自己的数据源,只要对主字段copy_to就能轻而易举的达到相同的效果
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name", (1)
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
},
"full_name": {
"type": "string"
}
}
}
}
}
跨字段查询
搜索时相应的解决方案:使用cross_fields进行multi_match查询。
cross_fields是词中心式的查询,它将所有字段当成一个大字段,并在每个字段中查询每个词
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "most_fields",
"operator": "and", (1)
"fields": [ "first_name", "last_name" ]
}
}
}
得到结果表示peter和smith都必须同时出现在相同字段中,要么是first_name,要么last_name
(+first_name:peter +first_name:smith)
(+last_name:peter +last_name:smith)
然而词中心式会使用以下逻辑,意味着peter和smith都必须出现,但是可以出现在任意字段中
+(first_name:peter last_name:peter)
+(first_name:smith last_name:smith)
cross_fields类型首先分析查询字符串并生成一个词列表,然后它从所有字段中依次搜索每个词。
这种不同的查询方式自然就解决了前两个问题。
至于IDF问题,它通过混合不同字段逆向索引文档频率的方式解决了词频的问题。
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields", (1)
"operator": "and",
"fields": [ "first_name", "last_name" ]
}
}
}
+blended("peter", fields: [first_name, last_name])
+blended("smith", fields: [first_name, last_name])
换句话说,它会在两个字段字段中查找smith的IDF,然后使用最小值作为两个字段的IDF。
Tip:为了让cross_fields查询以最优的方式工作,所有的字段都必须使用相同的分析器,具有相同分析器的字段会被分组在一起作为混合字段使用。
如果包括了不同分析器的字段,它们会以best_fields的相同方式加入到查询结果中。
例如将title字段加入到之前的查询,explanation解释如下:
(+title:peter +title:smith)
(
+blended("peter", fields: [first_name, last_name])
+blended("smith", fields: [first_name, last_name])
)
当在使用minimum_should_match和operator参数时,这点尤为重要
按字段提高权重
使用cross_fields和_all相比,其中一个优势就是它可以在搜索时为单个字段提升权重
正如前文提到的^
自定义单字段查询能否优于多字段查询,取决于在多字段与单字段自定义_all之间代价的权衡。
Exact-Value精确值字段
将not_analyzed字段与 multi_match中analyzed字段混在一起没有多大用处。
如前文的查询,假如title是not_analyzed
title:peter smith
(
blended("peter", fields: [first_name, last_name])
blended("smith", fields: [first_name, last_name])
)
es会将peter smith完整的字符串作为查询条件来搜索,所以需要在multi_match查询中避免使用not_analyzed字段