关于Elasticsearch查找相关的问题汇总(match、match_phrase、query_string和term)

发布时间 2023-04-10 23:59:37作者: oktokeep

关于Elasticsearch查找相关的问题汇总(match、match_phrase、query_string和term)

查询全部:
{
  "query": {
    "match_all": {}
  }
}
对应的 ​​QueryBuilder​​ Class 为 ​​MatchAllQueryBuilder​​
具体方法为 ​ ​QueryBuilders.matchAllQuery()​​


{
  "query": {
    "match": {
      "orderEventDispatchStatusDTO.consoleOperatorName": "李龙1"
    }
  }
}
对应的 ​​QueryBuilder​​ class : ​ ​MatchQueryBuilder​​
具体方法 : ​ ​QueryBuilders.matchQuery()​​

Search Query		QueryBuilder Class					Method in QueryBuilders
Match​​				​MatchQueryBuilder​​		​			QueryBuilders.matchQuery()​​
​Match Phrase​​	​	MatchPhraseQueryBuilder​​	​			QueryBuilders.matchPhraseQuery()​​
​Match Phrase Prefix​​	​MatchPhrasePrefixQueryBuilder​​		QueryBuilders.matchPhrasePrefixQuery()​​
​Multi Match​​			​MultiMatchQueryBuilder​​				​QueryBuilders.multiMatchQuery()​​
​Common Terms​​		​CommonTermsQueryBuilder​​				QueryBuilders.commonTermsQuery()​​
​​Query String​​		QueryStringQueryBuilder​​				​QueryBuilders.queryStringQuery()​​
​​Simple Query 		String​​​SimpleQueryStringBuilder​​		​QueryBuilders.simpleQueryStringQuery()​​


基于词项的查询
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()​​


Search Query	QueryBuilder Class		Method in QueryBuilders
​Term​​		​TermQueryBuilder​​		​QueryBuilders.termQuery()​​
​ ​Terms​​		​TermsQueryBuilder​​		​QueryBuilders.termsQuery()​​
​ ​Range​​		RangeQueryBuilder​​		QueryBuilders.rangeQuery()​​
​ ​Exists​​		​ExistsQueryBuilder​​		QueryBuilders.existsQuery()​​
​ ​Prefix​​		PrefixQueryBuilder​​		QueryBuilders.prefixQuery()​​
​ ​Wildcard​​	WildcardQueryBuilder​​	QueryBuilders.wildcardQuery()​​
​ ​Regexp​​		RegexpQueryBuilder​​		​QueryBuilders.regexpQuery()​​
​ ​Fuzzy​​		FuzzyQueryBuilder​​		QueryBuilders.fuzzyQuery()​​
​ ​Type​​		TypeQueryBuilder​​		QueryBuilders.typeQuery()​​
​ ​Ids​​		IdsQueryBuilder​​			QueryBuilders.idsQuery()​​


复合查询
什么是复合查询?
复合查询会将其他的复合查询或者叶查询包裹起来,以嵌套的形式展示和执行,得到的结果也是对各个子查询结果和分数的合并。可以分为下面几种:

constant_score query		经常用在使用 filter 的场合,所有匹配的文档分数都是一个不变的常量
​类-方法:​Constant Score​​		​ConstantScoreQueryBuilder​​	​QueryBuilders.constantScoreQuery()​​

bool query可以将多个叶查询和组合查询再组合起来,可接受的参数如下
must: 文档必须匹配这些条件才能被包含进来
​​must_not​​ 文档必须不匹配才能被包含进来
​​should​​ 如果满足其中的任何语句,都会增加分数;即使不满足,也没有影响
​​filter​​ 以过滤模式进行,不评分,但是必须匹配
类-方法:Bool​​	​BoolQueryBuilder​​	QueryBuilders.boolQuery()​​


dis_max query
叫做分离最大化查询,它会将任何与查询匹配的文档都作为结果返回,但是只是将其中最佳匹配的评分作为最终的评分返回。
类-方法: ​Dis Max​​		DisMaxQueryBuilder​​	 ​	QueryBuilders.disMaxQuery()​​

function_score query
允许为每个与主查询匹配的文档应用一个函数,可用来改变甚至替换原始的评分
​类-方法:Function Score​​		FunctionScoreQueryBuilder​​		​QueryBuilders.functionScoreQuery()​​

boosting query
用来控制(提高或降低)复合查询中子查询的权重。
​类-方法:Boosting​​	BoostingQueryBuilder​​		QueryBuilders.boostingQuery()​​


特殊查询
Wrapper Query
这里比较重要的一个是 ​ ​Wrapper Query​​,是说可以接受任何其他 base64 编码的字符串作为子查询。
主要应用场合就是在 Rest High-Level REST client 中接受 json 字符串作为参数。比如使用 gson 等 json 库将要查询的语句拼接好,直接塞到 Wrapper Query 中查询就可以了,非常方便。

​Wrapper Query​​ 对应的 ​​QueryBuilder​​ class 是​ ​WrapperQueryBuilder​​
具体方法是 ​ ​QueryBuilders.wrapperQuery()​​


一)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" : 1
        "searchField" : "abc#def"
    },
    {
        "id" : 2
        "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": 256
          }
        }
      }
 }
②将该字段设置成 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/1{
  "type":"apple"}
PUT test_normalizer/doc/2{
  "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/1{
  "type": "apple",
  "type_normalizer": "apple"}PUT test_normalizer/doc/2{
  "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也是小写的
两边分词匹对,就得到了我们上面的结果


### 扩展
"brandTxt": {
	"type": "text",
	"analyzer": "ik_max_word"
},
"description": {
	"type": "text",
	"analyzer": "ik_max_word",
	"search_analyzer": "ik_smart"
},
"address": {
	"type": "text",
	"analyzer": "ik_max_word",
	"include_in_all": false
},
"typeTxt": {
	"type": "text",
	"analyzer": "ik_max_word"
},