Elasticsearch深入搜索之全文搜索及JavaAPI使用
一、基于词项与基于全文
所有查询会或多或少的执行相关度计算,但不是所有查询都有分析阶段。 和一些特殊的完全不会对文本进行操作的查询(如 bool
或 function_score
)不同,文本查询可以划分成两大家族:
- 1.基于词项的查询
-
如
term
或fuzzy
这样的底层查询不需要分析阶段,它们对单个词项进行操作。用term
查询词项Foo
只要在倒排索引中查找 准确词项 ,并且用 TF/IDF 算法为每个包含该词项的文档计算相关度评分_score
。记住
term
查询只对倒排索引的词项精确匹配,这点很重要,它不会对词的多样性进行处理(如,foo
或FOO
)。这里,无须考虑词项是如何存入索引的。如果是将["Foo","Bar"]
索引存入一个不分析的(not_analyzed
)包含精确值的字段,或者将Foo Bar
索引到一个带有whitespace
空格分析器的字段,两者的结果都会是在倒排索引中有Foo
和Bar
这两个词。 - 2.基于全文的查询
-
像
match
或query_string
这样的查询是高层查询,它们了解字段映射的信息:一旦组成了词项列表,这个查询会对每个词项逐一执行底层的查询,再将结果合并,然后为每个文档生成一个最终的相关度评分。
我们将会在随后章节中详细讨论这个过程。
我们很少直接使用基于词项的搜索,通常情况下都是对全文进行查询,而非单个词项,这只需要简单的执行一个高层全文查询(进而在高层查询内部会以基于词项的底层查询完成搜索)。
当我们想要查询一个具有精确值的 not_analyzed
未分析字段之前, 需要考虑,是否真的采用评分查询,或者非评分查询会更好。
单词项查询通常可以用是、非这种二元问题表示,所以更适合用过滤, 而且这样做可以有效利用缓存:
GET /_search { "query": { "constant_score": { "filter": { "term": { "gender": "female" } } } } }
二、匹配查询
匹配查询 match
是个 核心 查询。无论需要查询什么字段, match
查询都应该会是首选的查询方式。 它是一个高级 全文查询 ,这表示它既能处理全文字段,又能处理精确字段。
1.先添加索引数据
DELETE /my_index PUT /my_index { "settings": { "number_of_shards": 1 }} //设置主分片个数,默认是5个,主分片副本数(number_of_replicas)默认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" }
GET /my_index/my_type/_search { "query": { "match": { "title": "QUICK!" } } } 查询结果: { "took": 19, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 0.42327404, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 0.42327404, "_source": { "title": "The quick brown fox" } }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.42211798, "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 0.2887157, "_source": { "title": "The quick brown fox jumps over the lazy dog" } } ] } }
Elasticsearch 执行上面这个 match
查询的步骤是:
-
检查字段类型 。
标题
title
字段是一个string
类型(analyzed
)已分析的全文字段,这意味着查询字符串本身也应该被分析。 -
分析查询字符串 。
将查询的字符串
QUICK!
传入标准分析器中,输出的结果是单个项quick
。因为只有一个单词项,所以match
查询执行的是单个底层term
查询。 -
查找匹配文档 。
用
term
查询在倒排索引中查找quick
然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。 -
为每个文档评分 。
用
term
查询计算每个文档相关度评分_score
,这是种将 词频(term frequency,即词quick
在相关文档的title
字段中出现的频率)和反向文档频率(inverse document frequency,即词quick
在所有文档的title
字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式
结果分析:1.文档 1 最相关,因为它的 title
字段更短,即 quick
占据内容的一大部分。
2.文档 3 比 文档 2 更具相关性,因为在文档 2 中 quick
出现了两次。
单词匹配JavaAPI应用
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title","QUICK!");
三、多词查询
1.
GET /my_index/my_type/_search { "query": { "match": { "title": "BROWN DOG!" } } } 查询结果: { "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 4, "max_score": 0.58571666, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "4", "_score": 0.58571666, "_source": { "title": "Brown fox brown dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 0.37400126, "_source": { "title": "The quick brown fox jumps over the lazy dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.37400126, "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 0.12503365, "_source": { "title": "The quick brown fox" } } ] } }
查询结果分析:
因为 match
查询必须查找两个词( ["brown","dog"]
),它在内部实际上先执行两次 term
查询,然后将两次查询的结果合并作为最终结果输出。为了做到这点,它将两个 term
查询包入一个 bool
查询中。
注:即任何文档只要 title
字段里包含 指定词项中的至少一个词 就能匹配,被匹配的词项越多,文档就越相关。
2.提高精度
GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "BROWN DOG!", "operator": "and" } } } } 查询结果: { "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 0.58571666, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "4", "_score": 0.58571666, "_source": { "title": "Brown fox brown dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 0.37400126, "_source": { "title": "The quick brown fox jumps over the lazy dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.37400126, "_source": { "title": "The quick brown fox jumps over the quick dog" } } ] } }
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title","BROWN DOG!").operator(Operator.AND); //AND必须全部包含,OR至少包含一个
使用 operator
操作符参数查询,and参数指必须包含查询的所有词才能被查询出来。
3.控制精度
GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "quick brown dog", "minimum_should_match": "75%" } } } } 查询结果: { "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 4, "max_score": 0.7961193, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.7961193, "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 0.662717, "_source": { "title": "The quick brown fox jumps over the lazy dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "4", "_score": 0.58571666, "_source": { "title": "Brown fox brown dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 0.54830766, "_source": { "title": "The quick brown fox" } } ] } }
MatchQueryBuilder matchQueryBuilder1 = QueryBuilders.matchQuery("title","quick brown dog").minimumShouldMatch("75%");
查询结果分析:
当给定百分比的时候, minimum_should_match
会做合适的事情:在之前三词项的示例中, 75%
会自动被截断成 66.6%
,即三个里面两个词。无论这个值设置成什么,至少包含一个词项的文档才会被认为是匹配的。
参数 minimum_should_match
的设置非常灵活,可以根据用户输入词项的数目应用不同的规则。完整的信息参考文档https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-minimum-should-match.html#query-dsl-minimum-should-match
四、组合查询
1.在组合过滤中,我们讨论过如何使用 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" }} ] } } } 查询结果: { "took": 19, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 0.7961192, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.7961192, "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 0.54830766, "_source": { "title": "The quick brown fox" } } ] } }
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("title","quick")); boolQueryBuilder.mustNot(QueryBuilders.matchQuery("title","lazy")); boolQueryBuilder.should(QueryBuilders.matchQuery("title","brown")); boolQueryBuilder.should(QueryBuilders.matchQuery("title","dog"));
查询结果分析:
查询结果返回 title
字段包含词项 quick
但不包含 lazy
的任意文档。目前为止,这与 bool
过滤器的工作方式非常相似。
区别就在于两个 should
语句,也就是说:一个文档不必包含 brown
或 dog
这两个词项,但如果一旦包含,我们就认为它们 更相关
评分计算:
bool
查询会为每个文档计算相关度评分 _score
, 再将所有匹配的 must
和 should
语句的分数 _score
求和,最后除以 must
和 should
语句的总数。
must_not
语句不会影响评分; 它的作用只是将不相关的文档排除。
2.控制精度
GET /my_index/my_type/_search { "query": { "bool": { "should": [ { "match": { "title": "brown" }}, { "match": { "title": "fox" }}, { "match": { "title": "dog" }} ], "minimum_should_match": 2 } } } 查询结果: { "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 4, "max_score": 0.71075034, "hits": [ { "_index": "my_index", "_type": "my_type", "_id": "4", "_score": 0.71075034, "_source": { "title": "Brown fox brown dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "2", "_score": 0.45928687, "_source": { "title": "The quick brown fox jumps over the lazy dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "3", "_score": 0.45928687, "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_index": "my_index", "_type": "my_type", "_id": "1", "_score": 0.2500673, "_source": { "title": "The quick brown fox" } } ] } }
查询结果分析:
通过 minimum_should_match
参数控制需要匹配的 should
语句的数量, 它既可以是一个绝对的数字,又可以是个百分比;
五、如何使用布尔匹配
1.多词match查询只是简单地将生成的 term
查询包裹 在一个 bool
查询中。如果使用默认的 or
操作符,每个 term
查询都被当作 should
语句,这样就要求必须至少匹配一条语句。以下两个查询是等价的:
{ "match": { "title": "brown fox"} } { "bool": { "should": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } }
2.如果使用 and
操作符,所有的 term
查询都被当作 must
语句,所以 所有(all) 语句都必须匹配。以下两个查询是等价的:
{ "match": { "title": { "query": "brown fox", "operator": "and" } } } { "bool": { "must": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } }
3.如果指定参数 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 } }
六、查询语句提升权重
1.一个简单的 bool
查询 允许我们写出如下这种非常复杂的逻辑:
GET /_search { "query": { "bool": { "must": { "match": { "content": { "query": "full text search", "operator": "and" } } }, "should": [ { "match": { "content": "Elasticsearch" }}, { "match": { "content": "Lucene" }} ] } } }
注:(1)content
字段必须包含 full
、 text
和 search
所有三个词。 (2)如果content字段包含Elasticsearch或者Lucene,文档会获得更高的评分_scorce.
我们可以通过指定 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 } }} ] } } }
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("content","full text search").operator(Operator.AND)); boolQueryBuilder.should(QueryBuilders.matchQuery("content","Elasticsearch").boost(3)); boolQueryBuilder.should(QueryBuilders.matchQuery("content","Lucene").boost(2));
注:(1)这些语句使用默认的 boost
值 1
。 (2)这条语句更为重要,因为它有最高的 boost
值。 (3)这条语句比使用默认值的更重要,但它的重要性不及 Elasticsearch
语句。
boost
参数被用来提升一个语句的相对权重( boost
值大于 1
)或降低相对权重( boost
值处于 0
到 1
之间),但是这种提升或降低并不是线性的,换句话说,如果一个 boost
值为 2
,并不能获得两倍的评分 _score
。
七、控制分析
查询只能查找倒排索引表中真实存在的项, 所以保证文档在索引时与查询字符串在搜索时应用相同的分析过程非常重要,这样查询的项才能够匹配倒排索引中的项。
尽管是在说 文档 ,不过分析器可以由每个字段决定。 每个字段都可以有不同的分析器,既可以通过配置为字段指定分析器,也可以使用更高层的类型(type)、索引(index)或节点(node)的默认配置。在索引时,一个字段值是根据配置或默认分析器分析的。
在之前的IK分词器和拼音分词器结合应用文章中有介绍,指定不同字段分词器分析,在此就不再说了