全文搜索有两个重要的方面是:
1.相关性:评价查询和结果间的相关程度
2.分析:将文本块转换成有区别、规范化的token的过程。目的是为了创建倒排索引和查询倒排索引。
基于词项的查询:如term或fuzzy这样的底层查询不需要分析阶段,它们对单个词进行操作,用term查询词项Foo只需要在倒排索引中查找准确词项,并且用TF/IDF算法为每个包含该词项的文档计算相关度评分_score。
基于全文的查询:像match或query_string这样的查询为高层查询,
如果是查询日期或整数字段,它们会将查询字符串分别作为日期或整数对待。
如果是查询一个未分析的精确值字符串字段,它们会将整个查询字符串作为单个单词项对待。
如果是查询一个已分析的全文字段,它们会先将查询字符串传递到一个合适的分析器,然后生成一个供查询的词项列表。一旦组成了词项列表,这个查询会对每个词项逐一执行底层的查询,再将结果合并,然后为每个文档生成一个最终的相关度评分。
我们很少直接使用基于词项的搜索,通常情况下都是对全文进行查询,而非单个词项,这只需要简单的执行一个高层全文查询(进而在高层查询内部会以基于词项的底层查询完成搜索)。
当我们想要查询一个具有精确值的 not_analyzed
未分析字段之前,需要考虑,是否真的采用评分查询,或者非评分查询会更好。
单词项查询通常可以用是、非这种二元问题表示,所以更适合用过滤,而且这样做可以有效利用[缓存]:
GET /_search
{
"query": {
"constant_score": {
"filter": {
"term": { "gender": "female" }
}
}
}
}
匹配查询
match查询是一个高级的全文查询,它既能处理全文字段,又能处理精确字段。
match查询的主要应用场景就是进行全文搜索。
首先创建一些新的文档和索引:
DELETE /my_index // 删除已有的索引
PUT /my_index
{ "settings": { "number_of_shards": 1 }}
POST /my_index/my_type/_bulk
{ "index": { "_id": 1 }}
{ "title": "The quick brown fox" }
{ "index": { "_id": 2 }}
{ "title": "The quick brown fox jumps over the lazy dog" }
{ "index": { "_id": 3 }}
{ "title": "The quick brown fox jumps over the quick dog" }
{ "index": { "_id": 4 }}
{ "title": "Brown fox brown dog" }
使用match查询搜索全文字段中的单个词:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "QUICK!"
}
}
}
es执行这个查询的步骤是:
1.检查字段的类型,是一个string类型的已分析全文字段。
2.分析字符串,将QUICK传入标准分析器进行分析,输出的结果为单个项quick,match查询的底层是执行单个term查询。
3.用term查询为每个文档计算相关度评分_score。
查询的结果为:
"hits": [
{
"_id": "1",
"_score": 0.5,
"_source": {
"title": "The quick brown fox" //title 字段更短,quick占据内容的一大部分。
}
},
{
"_id": "3",
"_score": 0.44194174,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "2",
"_score": 0.3125,
"_source": {
"title": "The quick brown fox jumps over the lazy dog"
}
}
]
多词查询
match查询可以进行多词查询
例如
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "BROWN DOG!"
}
}
}
查询的结果为:
{
"hits": [
{
"_id": "4",
"_score": 0.73185337,
"_source": {
"title": "Brown fox brown dog"
}
},
{
"_id": "2",
"_score": 0.47486103,
"_source": {
"title": "The quick brown fox jumps over the lazy dog"
}
},
{
"_id": "3",
"_score": 0.47486103,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "1",
"_score": 0.11914785,
"_source": {
"title": "The quick brown fox"
}
}
]
}
它在内部实际上是先执行了两次term查询,然后将两次查询的结果合并作为最终的结果输出。
任何文档只要title字段中包含指定词项中的至少一个词就能匹配,被匹配的词项越多,文档的相关程度越高。
提高精度
用 任意 查询词项匹配文档可能会导致结果中出现不相关的长尾。这是种散弹式搜索。可能我们只想搜索包含 所有 词项的文档,也就是说,不去匹配 brown OR dog
,而通过匹配 brown AND dog
找到所有文档。
match
查询还可以接受 operator
操作符作为输入参数,默认情况下该操作符是 or
。我们可以将它修改成 and
让所有指定词项都必须匹配:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "BROWN DOG!",
"operator": "and"
}
}
}
}
这个查询可以把文档 1 排除在外,因为它只包含两个词项中的一个。
控制精度
在 所有 与 任意 间二选一有点过于非黑即白。如果用户给定 5 个查询词项,想查找只包含其中 4 个的文档,这时候match
查询支持 minimum_should_match
最小匹配参数,这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown dog",
"minimum_should_match": "75%"
}
}
}
}
组合查询
在组合过滤器一节我们讨论过bool过滤器通过and、or和not逻辑组合将多个过滤器进行组合。
bool查询:bool查询也可以接受 must
、 must_not
和 should
参数下的多个查询语句。
比如:
GET /my_index/my_type/_search
{
"query": {
"bool": {
"must": { "match": { "title": "quick" }},
"must_not": { "match": { "title": "lazy" }},
"should": [
{ "match": { "title": "brown" }},
{ "match": { "title": "dog" }}
]
}
}
}
以上的查询结果返回 title 字段包含词项 quick 但不包含 lazy 的任意文档。目前为止,这与 bool 过滤器的工作方式非常相似。
区别就在于两个 should
语句,也就是说:一个文档不必包含brown
或 dog
这两个词项,但如果一旦包含,我们就认为它们 更相关 :
查询结果为:
{
"hits": [
{
"_id": "3",
"_score": 0.70134366,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "1",
"_score": 0.3312608,
"_source": {
"title": "The quick brown fox"
}
}
]
}
文档 3 会比文档 1 有更高评分是因为它同时包含 brown 和 dog 。
控制精度:
所有 must
语句必须匹配,所有 must_not
语句都必须不匹配,但有多少 should
语句应该匹配呢?默认情况下,没有 should
语句是必须匹配的,只有一个例外:那就是当没有 must
语句的时候,至少有一个 should
语句必须匹配。
我们可以通过 minimum_should_match
参数控制需要匹配的 should
语句的数量,它既可以是一个绝对的数字,又可以是个百分比:
GET /my_index/my_type/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "brown" }},
{ "match": { "title": "fox" }},
{ "match": { "title": "dog" }}
],
"minimum_should_match": 2
}
}
}
这个查询结果会将所有满足以下条件的文档返回: title 字段包含 "brown" AND "fox" 、 "brown" AND "dog" 或 "fox" AND "dog" 。如果有文档包含所有三个条件,它会比只包含两个的文档更相关。
使用布尔匹配
我们前面已经看到,多词match查询只是简单的将生成的term查询包裹在一个bool查询中,如果使用默认的or操作符,每个term查询都被当做should语句,这样就要求至少匹配一条语句。
下面的两个查询是等价的:
{
"match": { "title": "brown fox"}
}
{
"bool": {
"should": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
如果使用and操作符,所有的term查询都被当作must语句,所以所有的语句都必须匹配。
下面的两个查询是等价的:
{
"match": {
"title": {
"query": "brown fox",
"operator": "and"
}
}
}
{
"bool": {
"must": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
如果指定参数minimum_should_match,它可以通过 bool 查询直接传递,使以下两个查询等价:
{
"match": {
"title": {
"query": "quick brown fox",
"minimum_should_match": "75%"
}
}
}
{
"bool": {
"should": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }},
{ "term": { "title": "quick" }}
],
"minimum_should_match": 2
}
}
因为只有三条语句,match 查询的参数 minimum_should_match 值 75% 会被截断成 2 。即三条 should 语句中至少有两条必须匹配。
查询语句提升权重
假设想要查询关于 “full-text search(全文搜索)” 的文档,但我们希望为提及 “Elasticsearch” 或 “Lucene” 的文档给予更高的 权重 ,这里 更高权重 是指如果文档中出现 “Elasticsearch” 或 “Lucene” ,它们会比没有的出现这些词的文档获得更高的相关度评分 _score
,也就是说,它们会出现在结果集的更上面。
一个简单的 bool 查询 允许我们写出如下这种非常复杂的逻辑:
GET /_search
{
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [
{ "match": { "content": "Elasticsearch" }},
{ "match": { "content": "Lucene" }}
]
}
}
}
content必须包含full 、text、 search三个词。
如果content也包含Elasticsearch或Lucene,文档会获得更高的评分。
如果让包含Lucene的有更高的权重,并且包含Elasticsearch的语句比Lucene的权重更高,我们就需要指定boost来控制查询语句的相对权重。boost的默认值是1,大于1会提升一个语句的相对权重。
GET /_search
{
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [
{ "match": {
"content": {
"query": "Elasticsearch",
"boost": 3 //权重最高
}
}},
{ "match": {
"content": {
"query": "Lucene",
"boost": 2
}
}}
]
}
}
}
boost参数也可以用来降低相对权重(boost值在0和1之间)但是这种提升或降低不是线性的,也就是说如果一个boost值为2,它并不能获得2倍的评分。
控制分析:
查询只能查找倒排索引表中真实存在的项,所以保证文档在索引时与查询字符串在搜索时应用相同的分析过程非常重要,这样查询的项才能够匹配倒排索引中的项。
尽管是在说 文档 ,不过分析器可以由每个字段决定。每个字段都可以有不同的分析器,既可以通过配置为字段指定分析器,也可以使用更高层的类型(type)、索引(index)或节点(node)的默认配置。在索引时,一个字段值是根据配置或默认分析器分析的。
例如为 my_index 新增一个字段:
PUT /my_index/_mapping/my_type
{
"my_type": {
"properties": {
"english_title": {
"type": "string",
"analyzer": "english"
}
}
}
}
现在我们就可以通过使用 analyze API 来分析单词 Foxes ,进而比较 english_title 字段和 title 字段在索引时的分析结果:
GET /my_index/_analyze
{
"field": "my_type.title", //使用默认的 standard 标准分析器,返回词项 foxes 。
"text": "Foxes"
}
GET /my_index/_analyze
{
"field": "my_type.english_title", //使用 english 英语分析器,返回词项 fox 。
"text": "Foxes"
}
这意味着,如果使用底层 term 查询精确项 fox 时, english_title 字段会匹配但 title 字段不会。
默认分析器:
虽然我们可以在字段层级指定分析器,但是如果该层级没有指定任何的分析器,那么我们如何能确定这个字段使用的是哪个分析器呢?
分析器可以从三个层面进行定义:按字段(per-field)、按索引(per-index)或全局缺省(global default)。Elasticsearch 会按照以下顺序依次处理,直到它找到能够使用的分析器。
索引时的顺序如下:
1.字段映射里定义的analyzer
2.索引设置中名为default的分析器,默认为standard标准分析器。
搜索时的顺序:
1.查询自己定义的analyzer
2.字段映射中定义的analyzer
3.索引设置中名为default的分析器,默认为standard标准分析器。
有时,在索引时和搜索时使用不同的分析器是合理的。我们可能要想为同义词建索引(例如,所有 quick
出现的地方,同时也为 fast
、 rapid
和 speedy
创建索引)。但在搜索时,我们不需要搜索所有的同义词,取而代之的是寻找用户输入的单词是否是 quick
、 fast
、 rapid
或 speedy
。
为了区分,Elasticsearch 也支持一个可选的 search_analyzer
映射,它仅会应用于搜索时( analyzer
还用于索引时)。还有一个等价的 default_search
映射,用以指定索引层的默认配置。
如果考虑到这些额外参数,一个搜索时的 完整 顺序会是下面这样:
查询自己定义的 analyzer ,否则
字段映射里定义的 search_analyzer ,否则
字段映射里定义的 analyzer ,否则
索引设置中名为 default_search 的分析器,默认为standard 标准分析器
索引设置中名为 default 的分析器,默认为standard 标准分析器
.