1. 借鉴
极客时间 阮一鸣老师的Elasticsearch核心技术与实战
Elasticsearch搜索引擎第十篇-Query DSL详解
如何使Elasticsearch的前缀查询(prefix query)效果同sql的like 'prefix%'
官方文档 term-level-queries
官方文档 regexp语法
Elasticsearch:fuzzy 搜索 (模糊搜索)
盘点Elasticsearch中的查询套路
官方文档 rewrite
2. 开始
基于term的查询
- term级别的查询有以下几种:term/terms/terms set,range,exists,prefix,wildcard,regexp,fuzzy,ids。
-
对输入不做分词
,这点要注意
-
接下来,我们以下列数据为例,来介绍以下基于term的查询.
POST /product/_bulk
{"index": { "_id": 1 }}
{"produceId":"HKXL-1234-SKOX", "name": "测试产品1", "date": "2019-05-20", "price": 10}
{"index": { "_id": 2 }}
{"produceId":"UJSK-1234-BSUA", "name": "测试产品2", "price": 20, "date": "2020-04-15"}
{"index": { "_id": 3 }}
{"produceId":"HSYA-1234-MLBS", "name": "测试产品3", "price": 40, "date": "2020-01-20"}
{"index": { "_id": 4 }}
{"produceId":"HSYA-1234-MLBS", "name": "测试产品4", "price": 40, "date": "2020-01-20", "tag": ["机械"]}
term
GET /product/_search
{
"query": {
"term": {
"name": {
"value": "测试产品4"
}
}
}
}
- 查询结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
- 嗯,啥也没有,是不是觉得很奇怪,不是不对输入进行分词吗?为啥查不到呢?我们前有说过索引,对text类型,es会进行分词,默认使用standard分词器进行分词,我们来看一下standard分词器对”测试产品4“的分词结果
GET /_analyze
{
"text": "测试产品4",
"analyzer": "standard"
}
- 分词结果
{
"tokens" : [
{
"token" : "测",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "试",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "产",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "品",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "4",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<NUM>",
"position" : 4
}
]
}
- 可以看到是每个字是单独成词,term对输入不进行分词,索引没有匹配上的关键字,自然也就没有文档被检索出来,那我们该如何查询出来呢?可以使用keyword属性进行查询,但必须是精确的【啥意思?就是你要搜索”测试产品4“,必须是”测试产品4“这5个字符全部】
GET /product/_search
{
"query": {
"term": {
"name.keyword": {
"value": "测试产品4"
}
}
}
}
terms
了解了term,我们来看下terms查询。term是单值匹配,terms就是多值。
GET /product/_search
{
"query": {
"terms": {
"name": [
"好优肯",
"4"
]
}
}
}
terms set
- 这里直接翻译官网的话了,更容易理解
terms_set查询与terms查询相同,只是您可以定义返回文档所需的匹配术语的数量。例如:
- 字段programming_languages包含一系列已知的编程语言,如c++、java或php,供求职者使用。您可以使用terms_set查询来返回至少匹配这两种语言的文档。
- 一个名为permissions的字段包含应用程序的可能用户权限列表。您可以使用terms_set查询来返回匹配这些权限子集的文档。
在大多数情况下,需要在索引中包含一个数字字段映射来使用terms_set查询。此数字字段包含返回文档所需的匹配项的数目。
# 创建索引
PUT /job-candidates
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"programming_languages": {
"type": "keyword"
},
"required_matches": {
"type": "long"
}
}
}
}
# 索引两篇文档
PUT /job-candidates/_doc/1?refresh
{
"name": "Jane Smith",
"programming_languages": ["c++", "java"],
"required_matches": 2
}
PUT /job-candidates/_doc/2?refresh
{
"name": "Jason Response",
"programming_languages": ["java", "php"],
"required_matches": 2
}
- 可以使用required_matches字段值作为返回terms_set查询中的文档所需的匹配项数量。
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": ["c++", "java", "php"],
"minimum_should_match_field": "required_matches"
}
}
}
}
- 我们也可以使用script脚本来查询
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": ["c++", "java", "php"],
"minimum_should_match_script": {
"source": "Math.min(params.num_terms, doc['required_matches'].value)"
}
}
}
}
}
range
# 对数字进行区间查询
GET /product/_search
{
"query": {
"range": {
"price": {
"gte": 10,
"lte": 20
}
}
}
}
# 对时间进行区间查询1
GET /product/_search
{
"query": {
"range": {
"date": {
"lte": "2020-04-20",
"gte": "2019-01-01"
}
}
}
}
#对时间进行区间查询2
# 查找一年以前的数据
GET /product/_search
{
"query": {
"range": {
"date": {
"gte": "now-1y"
}
}
}
}
- 时间表达式
表达式 | 释义 |
---|---|
y | 年 |
M | 月 |
w | 周 |
d | 天 |
H/h | 小时 |
m | 分钟 |
s | 秒 |
- 比较表达式
表达式 | 释义 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
exists
GET /product/_search
{
"query": {
"exists": {
"field": "tag"
}
}
}
prefix
前缀查询
GET /product/_search
{
"query": {
"prefix": {
"name": {
"value": "产"
}
}
}
}
- 查询结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "product",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"produceId" : "HKXL-1234-SKOX",
"name" : "测试产品1",
"date" : "2019-05-20",
"price" : 10
}
},
{
"_index" : "product",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"produceId" : "UJSK-1234-BSUA",
"name" : "测试产品2",
"price" : 20,
"date" : "2020-04-15"
}
},
{
"_index" : "product",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"produceId" : "HSYA-1234-MLBS",
"name" : "测试产品3",
"price" : 40,
"date" : "2020-01-20"
}
},
{
"_index" : "product",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"produceId" : "HSYA-1234-MLBS",
"name" : "测试产品4",
"price" : 40,
"date" : "2020-01-20",
"tag" : [
"机械"
]
}
}
]
}
}
- 等等,不是说前缀查询吗,您这也不是前缀查询啊,"测试产品","产"是第三个词啊。
- 嗯,对于es中的查询,不能忽略的就是他的分词结果,我们在上面确认了它的分词结果,是每个字,单独成词,所以对于每个字来说,他就是自己的前缀,所以,你搜”测试产品“中的任意字都是前缀,那如何来解决呢?大家可以修改分词器,可以参照我借鉴的文章,有关分词器的部分,后续也会更新一篇文章// TODO
wildcard
通配符查询
- “*”,它匹配任何字符序列(包括空字符)
- “?”,它匹配任何单个字符。
- 为了防止极慢的通配符查询,通配符项不应以通配符 “*” 或 “?” 开头
GET /product/_search
{
"query": {
"wildcard": {
"name.keyword": {
"value": "测试产品?"
}
}
}
}
regexp
正则查询
GET /product/_search
{
"query": {
"wildcard": {
"name.keyword": {
"value": "测试产品?"
}
}
}
}
我们来看下它都有哪些操作符【官方直译】
操作符 | 释义 | 例子 |
---|---|---|
. | 匹配任意字符 | - |
? | 重复0次或1次 | abc? matches 'ab' and 'abc' |
+ | 重复1次或多次 | ab+ matches 'ab', 'abb', 'abbb', ... |
* | 重复0次或多次 | ab* matches 'a', 'ab', 'abb', 'abbb', ... |
{} | 可重复的最小,最大次数 | a{2} matches 'aa' a{2,4} matches 'aa', 'aaa', and 'aaaa' a{2,} matches 'a` repeated two or more times |
| | 如果左边或右边最长的模式匹配,则匹配将成功 | abc|xyz matches 'abc' and 'xyz' |
( … ) | 形成一个组。可以使用组将表达式的一部分视为单个字符 | abc(def)? matches 'abc' and 'abcdef' but not 'abcd' |
[ … ] | 1.匹配括号中的一个字符 2.在方括号内—表示一个范围,除非—是第一个字符或转义字符 3.方括号中的字符前的^将使字符或范围无效。 |
1.[abc] matches 'a', 'b', 'c' 2.[a-c] matches 'a', 'b', or 'c' [-abc] '-' is first character. Matches '-', 'a', 'b', or 'c' [abc-] Escapes '-'. Matches 'a', 'b', 'c', or '-' 3.[^abc] matches any character except 'a', 'b', or 'c' [^a-c] matches any character except 'a', 'b', or 'c' [^-abc] matches any character except '-', 'a', 'b', or 'c' [^abc-] matches any character except 'a', 'b', 'c', or '-' |
我们可以使用flags参数为Lucene的正则表达式引擎启用更多的可选操作符。要启用多个操作符,使用|分隔符
GET /product/_search
{
"query": {
"regexp":{
"name.keyword": {
"value": "测.+&.+试.*",
"flags" : "INTERSECTION"
}
}
}
}
我们看下flags都有哪些类型
操作符 | 释义 | 例子 |
---|---|---|
ALL | 启用所有可选操作 | - |
COMPLEMENT | 启用~操作 | a~bc matches 'adc' and 'aec' but not 'abc' |
INTERVAL | 启用<>操作,可以用作匹配一个数字的区间 | foo<1-100> matches 'foo1', 'foo2' ... 'foo99', 'foo100' foo<01-100> matches 'foo01', 'foo02' ... 'foo99', 'foo100' |
INTERSECTION | 启用&操作,相当于AND | aaa.+&.+bbb matches 'aaabbb' |
ANYSTRING | 启用@操作 | @&~(abc.+) matches everything except terms beginning with 'abc' |
fuzzy
模糊查询
# 添加三篇测试文档
PUT /fuzz/_doc/1
{
"name": "sunruikai 123"
}
PUT /fuzz/_doc/2
{
"name": "gabriella 456"
}
PUT /fuzz/_doc/3
{
"name": "test kak"
}
- 执行查询
GET /fuzz/_search
{
"query": {
"fuzzy": {
"name": {
"value": "sunruilai"
}
}
}
}
当然,除了value它还有其他属性
属性 | 释义 |
---|---|
fuzziness | 允许匹配的最大编辑距离 |
max_expansions | 查询中的词项可以扩展的数目,默认为50【避免在max_expansions参数中使用高值,特别是当prefix_length参数值为0时。max_expansions参数中的高值会导致性能低下,因为要检查的变量太多。】 |
prefix_length | 指明区分词项的共同前缀长度,默认是0 |
transpositions | 表示编辑是否包含两个相邻字符的移位(ab→ba),默认为true |
rewrite | 有6中方式,constant_score(默认),constant_score_boolean,scoring_boolean,top_terms_blended_freqs_N,top_terms_boost_N,top_terms_N |
ids
见名知意,就是通过一堆id进行查询
GET /product/_search
{
"query": {
"ids": {
"values": [1,2,3]
}
}
}
costanct_score
不计算得分
GET /product/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"produceId.keyword": "HKXL-1234-SKOX"
}
}
}
}
}