由上图看出, QueryBuilder
是整个查询操作的核心,决定了查询什么样的数据和期望得到什么结果这些核心的问题。
QueryBuilder
只是一个接口,需要具体的实体类才可以。那么如何创建 QueryBuilder
的实例呢?有两种方式
- 通过
QueryBuilder
实现类的构造函数 - 使用
QueryBuilders
工具类创建
Building Queries
下面就来看下常用的查询及其 API 有哪些
匹配所有的查询
查询语句如下
GET /_search
{
"query": {
"match_all": {}
}
}
对应的 QueryBuilder
Class 为 MatchAllQueryBuilder
具体方法为 QueryBuilders.matchAllQuery()
全文查询 Full Text Queries
什么是全文查询?
像使用 match
或者 query_string
这样的高层查询都属于全文查询,
- 查询 日期(
date
) 或整数(integer
) 字段,会将查询字符串分别作为日期或整数对待。 - 查询一个(
not_analyzed
)未分析的精确值字符串字段,会将整个查询字符串作为单个词项对待。 - 查询一个(
analyzed
)已分析的全文字段,会先将查询字符串传递到一个合适的分析器,然后生成一个供查询的词项列表
组成了词项列表,后面就会对每个词项逐一执行底层查询,将查询结果合并,并且为每个文档生成最终的相关度评分。
Match
match
查询的单个词的步骤是什么?
- 检查字段类型,查看字段是
analyzed
,not_analyzed
- 分析查询字符串,如果只有一个单词项,
match
查询在执行时就会是单个底层的term
查询 - 查找匹配的文档,会在倒排索引中查找匹配文档,然后获取一组包含该项的文档
- 为每个文档评分
构建 Match 查询
match
查询可以接受 text/numeric/dates 格式的参数,分析,并构建一个查询。
GET /_search
{
"query": {
"match" : {
"message" : "this is a test"
}
}
}
上面的实例中 message
是一个字段名。
对应的 QueryBuilder
class : MatchQueryBuilder
具体方法 : QueryBuilders.matchQuery()
全文查询 API 列表
全部的 API 列表如下(链接均指向 elasticsearch 官网)
基于词项的查询
这种类型的查询不需要分析,它们是对单个词项操作,只是在倒排索引中查找准确的词项(精确匹配)并且使用 TF/IDF 算法为每个包含词项的文档计算相关度评分 _score
。
Term
term
查询可用作精确值匹配,精确值的类型则可以是数字,时间,布尔类型,或者是那些 not_analyzed 的字符串。
对应的 QueryBuilder
class 是TermQueryBuilder
具体方法是 QueryBuilders.termQuery()
Terms
terms
查询允许指定多个值进行匹配。如果这个字段包含了指定值中的任何一个值,就表示该文档满足条件。
对应的 QueryBuilder
class 是 TermsQueryBuilder
具体方法是 QueryBuilders.termsQuery()
Wildcard
wildcard
通配符查询是一种底层基于词的查询,它允许指定匹配的正则表达式。而且它使用的是标准的 shell 通配符查询:
-
?
匹配任意字符 -
*
匹配 0 个或多个字符
wildcard
需要扫描倒排索引中的词列表才能找到所有匹配的词,然后依次获取每个词相关的文档 ID。
由于通配符和正则表达式只能在查询时才能完成,因此查询效率会比较低,在需要高性能的场合,应当谨慎使用。
对应的 QueryBuilder
class 是 WildcardQueryBuilder
具体方法是 QueryBuilders.wildcardQuery()
基于词项 API 列表
复合查询
什么是复合查询?
复合查询会将其他的复合查询或者叶查询包裹起来,以嵌套的形式展示和执行,得到的结果也是对各个子查询结果和分数的合并。可以分为下面几种:
-
经常用在使用 filter 的场合,所有匹配的文档分数都是一个不变的常量
-
可以将多个叶查询和组合查询再组合起来,可接受的参数如下
- must : 文档必须匹配这些条件才能被包含进来
-
must_not
文档必须不匹配才能被包含进来 -
should
如果满足其中的任何语句,都会增加分数;即使不满足,也没有影响 -
filter
以过滤模式进行,不评分,但是必须匹配
-
叫做分离最大化查询,它会将任何与查询匹配的文档都作为结果返回,但是只是将其中最佳匹配的评分作为最终的评分返回。
-
允许为每个与主查询匹配的文档应用一个函数,可用来改变甚至替换原始的评分
-
用来控制(提高或降低)复合查询中子查询的权重。
复合查询列表
特殊查询
Wrapper Query
这里比较重要的一个是 Wrapper Query,是说可以接受任何其他 base64 编码的字符串作为子查询。
主要应用场合就是在 Rest High-Level REST client 中接受 json 字符串作为参数。比如使用 gson 等 json 库将要查询的语句拼接好,直接塞到 Wrapper Query 中查询就可以了,非常方便。
Wrapper Query 对应的 QueryBuilder
class 是WrapperQueryBuilder
具体方法是 QueryBuilders.wrapperQuery()
小结
本文对 elasticsearch rest high client 中的查询构建进行了总结和整理,对常用的 API 做了简要的介绍。读者如果要查看完整的构建查询的 API 列表,可参考此处
参考文档
一)text字段和keyword字段的区别
所以将字段设置成keyword的时候查询的时候已有的值不会被分词。而text类型的字段会被分词,查询的时候如果用拆开查可以查询的到,但是要是直接全部查,就是查询不到。
注意“1, 2”会被拆分成[1, 2],但是"1,2"是不拆分的,少了个空格。
一、match、match_phrase、query_string和term的区别
1、match和term的区别
1.1、term
1)term查询keyword字段。
term不会分词。而keyword字段也不分词。需要完全匹配才可。
2)term查询text字段
因为text字段会分词,而term不分词,所以term查询的条件必须是text字段分词后的某一个。
1.2.match
1)match查询keyword字段
match会被分词,而keyword不会被分词,match的需要跟keyword的完全匹配可以。
其他的不完全匹配的都是失败的。
2)match查询text字段
match分词,text也分词,只要match的分词结果和text的分词结果有相同的就匹配。
1.3.match_phrase
1)match_phrase匹配keyword字段。
这个同上必须跟keywork一致才可以。
2)match_phrase匹配text字段。
match_phrase是分词的,text也是分词的。match_phrase的分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的。
1.4.query_string
1)query_string查询keyword类型的字段,试过了,无法查询。
2)query_string查询text类型的字段。
和match_phrase区别的是,不需要连续,顺序还可以调换。
二、关于Elasticsearch的精确值查找(term)不生效问题
2.1、问题
常用的 term 查询, 可以用它处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。term查询数字的时候并没有什么问题,但是当我们对字符串类型的字段进行term查询时可能会得到意想不到的情况,可能明明有记录却查询不到,也可能查询出不符合预期的记录。
原因
ES会默认给每个字段进行分词然后建立倒排索引。比如,有两条JSON数据如下:
[
{
"id" :
"searchField" : "abc#def"
},
{
"id" :
"searchField" : "abc#xyz"
}
]
使用ES提供的analyze API 可以看到分词结果如下:
按照上面的分词结果,那么当我们将这两条数据插入ES的时候,建立的倒排索引如下:
Term Counter DocId
abc 2 1,2
def 1 1
xyz 1 2
注意:如果字段内容是大写的,那么在分词生成索引后,索引的项目会变成小写,比如上面的两条数据是ABC#DEF和ABC#XYZ,那么生成的索引也和上面的一样。此时由于索引项是小写,因此term查询ABC是查不到的,必须要查询abc;match查询ABC是可以查询到的,因为match会进行分词然后再匹配。
①使用term精确查询searchField为abc#def的记录:
{
"query": {
"term": {
"searchField":"abc#def"
}
}
}
此时得到的结果是空,我们无法获得期望的结果,问题不在 term 查询,而在于abc#def并不在我们的倒排索引中。
②使用term精确查询searchField为abc的记录:
{
"query": {
"term": {
"searchField":"abc"
}
}
}
此时得到的结果是两条数据都被检索出来。
根据建立的倒排索引不难发现,当精确匹配abc时,那么会命中如下的索引,它的DocId是1,2,因此会查出两条记录。
解决方案
①将字段的type设置为keyword
明确字段是否需要分词,不需要分词的字段就将type设置为keyword,可以节省空间和提高写性能。
ElasticSearch 5.0以后,String字段被拆分成两种新的数据类型: text用于全文搜索,会分词,而keyword用于关键词搜索,不进行分词。对于字符串类型的字段,ES默认会再生成一个keyword字段用于精确索引。默认mapping如下:
"mapping": {
"properties": {
"id": {
"type": "long"
},
"searchField": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above":
}
}
}
}
②将该字段设置成 not_analyzed 无需分析的
告诉 Elasticsearch该字段具有精确值,要将index属性设置成 not_analyzed 无需分析的。也是在mapping中进行设置,例如:
"mapping": {
"properties": {
"id": {
"type": "long"
},
"searchField": {
"type": "text",
"index": "not_analyzed"
}
}
如果是使用Java High Level REST Client 操作Elasticsearch的话可以参考官方API进行设置。
例如:
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
{
builder.startObject("properties");
{
builder.startObject("message");
{
builder.field("type", "text");
}
builder.endObject();
}
builder.endObject();
}
builder.endObject();
request.source(builder);
index 属性控制怎样索引字符串。它可以是下面三个值:
① analyzed:首先分析字符串,然后索引它。换句话说,以全文索引这个域。
② not_analyzed:索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。
③ no:不索引这个域。这个域不会被搜索到。
注意:其他简单类型(例如 long , double , date 等)也接受 index 参数,但有意义的值只有 no 和 not_analyzed , 因为它们永远不会被分析。
2.2、elasticsearch大小写无法使用term查询的问题
在 Elasticsearch中处理字符串类型的数据时,如果我们想把整个字符串作为一个完整的 term 存储,我们通常会将其类型 type
设定为 keyword
。但有时这种设定又会给我们带来麻烦,比如同一个数据再写入时由于没有做好清洗,导致大小写不一致,比如 apple
、Apple
两个实际都是 apple
,但当我们去搜索 apple
时却无法返回 Apple
的文档。
原因是elasticsearch在创建倒排索引时,就已经将大写转为小写,而后写入索引。解决方法:
- 一种是在传入查询条件的时候,使用toLowerCase()转化为小写,但是条件一多,代码量颇多,不太适用。
- 一种是在设置mapping的时候设置normalizer要解决这个问题。
PUT test_normalizer{
"mappings": {
"doc":{
"properties": {
"type":{
"type":"keyword"
}
}
}
}
}
PUT test_normalizer/doc/{
"type":"apple"}
PUT test_normalizer/doc/{
"type":"Apple"}
# 查询一 GET test_normalizer/_search{
"query": {
"match":{
"type":"apple"
}
}
}
# 查询二GET test_normalizer/_search{
"query": {
"match":{
"type":"aPple"
}
}}
原因:
-
Docs
写入Elasticsearch
时由于type
是keyword
,分词结果为原始字符串 - 查询 Query 时分词默认是采用和字段写时相同的配置,因此这里也是
keyword
,因此分词结果也是原始字符 - 两边的分词进行匹对,便得出了我们上面的结果
2. Normalizer
normalizer
是 keyword
的一个属性,可以对 keyword
生成的单一 Term
再做进一步的处理,比如 lowercase
,即做小写变换。使用方法和自定义分词器有些类似,需要自定义,如下所示:
DELETE test_normalizer
# 自定义 normalizer
PUT test_normalizer{
"settings": {
"analysis": {
"normalizer": {
"lowercase": {
"type": "custom",
"filter": [
"lowercase"
]
}
}
}
},
"mappings": {
"doc": {
"properties": {
"type": {
"type": "keyword"
},
"type_normalizer": {
"type": "keyword",
"normalizer": "lowercase"
}
}
}
}}
PUT test_normalizer/doc/{
"type": "apple",
"type_normalizer": "apple"}PUT test_normalizer/doc/{
"type": "Apple",
"type_normalizer": "Apple"}
# 查询三GET test_normalizer/_search{
"query": {
"term":{
"type":"aPple"
}
}}
# 查询四GET test_normalizer/_search{
"query": {
"term":{
"type_normalizer":"aPple"
}
}}
我们第一步是自定义了名为 lowercase
的 normalizer,其中filter
类似自定义分词器中的 filter
,但是可用的种类很少,详情大家可以查看官方文档。然后通过 normalizer
属性设定到字段type_normalizer
中,然后插入相同的2条文档。执行发现,查询三
无结果返回,查询四
返回2条文档。
文档写入时由于加入了
normalizer
,所有的term
都会被做小写处理查询时搜索词同样采用有
normalizer
的配置,因此处理后的term
也是小写的两边分词匹对,就得到了我们上面的结果
原文链接:https://blog.csdn.net/u011821334/article/details/100979286
参考:http://www.imooc.com/article/72059
参考:https://blog.csdn.net/AlbertFly/article/details/100705554?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4