搜索是 ES 最为复杂精妙的地方,这里只示例项目中较为常用的查询。
ES 中的查询分为三大类,一是 Term-level queries(我翻译成字段匹配),二是 Full-text queries(全文搜索),三是不常用的 Specialized queries(专门查询)。各自可细分为如下几种:
Term-level queries TermQuery 精确匹配单个字段TermsQuery 精确匹配单个字段,但使用多值进行匹配,类似于 SQL 中的 in *** 作RangeQuery 范围查询BoolQuery 组合查询ExistsQuery 字段是否存在值 Full-text queries MatchQuery 单字段搜索(匹配分词结果,不需要全文匹配) Specialized queries Script query 脚本查询这里并未将每个大类下的全部子类列举出来,只列举了本文涉及到查询。其他可到官网了解。
1.根据 ID 查询 1.1 获取单个文档根据文档 ID 获取单个文档信息。对应的 RESTful API 为:
GET <index>/_doc/<_id>
GET <index>/_source/<_id>
比如查询 index 为 es_index_userinfo 中文档 ID 为 1 的用户信息:
GET es_index_userinfo/_doc/1
如果只想返回部分字段,可以使用_source_includes
或_source_excludes
参数来包括或过滤掉特定字段。
例如不返回创建时间(create_time) 和更新时间(update_time),支持通配符。
GET /es_index_userinfo/_doc/1?_source_includes=*&_source_excludes=*time
翻译成 Go 为:
// GetByID4ES 根据ID查询单个文档
func GetByID4ES(ctx context.Context, index, id string) (string, error) {
res, err := GetESClient().
Get().
Index(index).
Id(id).
Do(ctx)
if err != nil {
return "", err
}
return string(res.Source), nil
}
注意:查询不存在的 ID,会报elastic: Error 404 (Not Found)
错误。
利用 Multi get 可以根据文档 ID 批量获取多个文档。
GET /_mget
{
"docs": [
{
"_index": "my-index-000001",
"_id": "1"
},
{
"_index": "my-index-000001",
"_id": "2"
}
]
}
GET /<index>/_mget
{
"docs": [
{
"_id": "1"
},
{
"_id": "2"
}
]
}
借助 MgetService 和 MultiGetItem 可实现批量获取文档。翻译成 Go 为:
mgetSvc := EsCli.MultiGet()
for _, id := range ids {
mgetSvc.Add(elastic.NewMultiGetItem().
Index(index).
Id(id))
}
rsp, err := mgetSvc.Do(ctx)
2.精确匹配单个字段
比如获指定用户名的用户。
// 创建 term 查询条件,用于精确查询
termQuery := elastic.NewTermQuery("username", "cat")
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(termQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query": {
"term": {"username": "bob"}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
注意: term 精确匹配 text 类型的字段可能匹配不到,因为 text 类型的字段会被分词.。如果分词的结果集中没有 term 指定的内容,那么将无法匹配。keyword 类型字段不会进行分词,所以可以用 term 进行精确匹配。
解决办法:给 text 类型的字段取一个别名,别名的类型为 keyword,即不进行分词。
"ancestral":{
"type": "text",
"fields": {
"alias": {
"type": "keyword"
}
}
}
那么可以通过 ancestral.alias 访问字段 ancestral,其类型设为 keyword。
3.精确匹配单个字段的多个值通过 TermsQuery 实现单个字段的多值精确匹配,类似于 SQL 的 in 查询。
比如获指定用户名的用户,只需要命中一个即可。
// 创建 terms 查询条件,用于多值精确查询
termsQuery := elastic.NewTermsQuery("username", "cat", "bob")
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(termsQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query": {
"terms": {"username": ["bobs","bob"]}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
4.全文查询
全文查询 Full text queries 是个 ES 的核心查询。
无论需要查询什么字段, MatchQuery 查询都应该会是首选的查询方式。它是一个高级全文查询 ,这表示它既能处理全文字段,又能处理精确字段。
使用 MatchQuery 对字段进行全文搜索,即匹配分词结果。如果分词出现在 MatchQuery 中指定的内容(指定的内容也会分词),如果存在相同的分词,则匹配。
假设“我爱中国”的分词结果为“我”、“爱”、“中国”,那么搜索“我是第一名”也会匹配,因为“我是第一名”的分词结果中也有“我”。
ES 查看某个字段数据的分词结果。
GET /{index}/{type}/{id}/_termvectors?fields={fields_name}
注意:
(1)如果想对输入不进行分词,请使用 term query;
(2)如果想对输入的分词结果全部匹配,请使用 match phrase query;
(3)如果想对输入的分词结果全部匹配且最后一个分词支持前缀匹配,请使用 match phrase prefix query;
(4)如果是对 keyword 字段进行 MatchQuery,因为该类型不会分词,所以是精确匹配。
比如获取指定用户名的用户。
// 创建 match 查询条件
matchQuery := elastic.NewMatchQuery("username", "bob")
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(matchQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query": {
"match": {"username": "bob"}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
5.范围查询
实现类似age >= 18 and age < 35
的范围查询条件。
// 创建 range 查询条件
rangeQuery := elastic.NewRangeQuery("age").Gte(18).Lte(35)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(rangeQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query": {
"range":{"age" : {"gte" : 18, "lte": 35}}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
6.判断某个字段是否存在
有时,我们需要查询不包含指定字段的记录,此时我们可以借助 Exists Query 来完成。其表示某个字段存在,如果表示不存在,需要借助 must_not boolean query 来完成。
注意:如果字段为 null 或者 [],虽然值为空,但是字段是存在的。
比如查询不存在 phone 字段且年龄大于 18 的用户记录,条件可以这么写:
GET /es_index_userinfo/_search
{
"query": {
"bool": {
"must_not": {"exists": {"field": "phone"}},
"filter":{"range":{"age":{"gte":18}}}
}
}
}
翻译成 Golang,对应的条件写为:
boolQuery := elastic.NewBoolQuery()
boolQuery.MustNot(elastic.NewExistsQuery("phone")))
boolQuery.Filter(elastic.NewRangeQuery("age").Gte(18))
7.bool 组合查询
BoolQuery 是一种组合查询,将多个条件通过类似 SQL 语句 and 和 or 组合在一起来作为查询条件。
其有四种类型的子句:
类型 | 描述 |
---|---|
must | 条件必须要满足,并将对分数起作用 |
filter | 条件必须要满足,但又不同于 must 子句,在 filter context 中执行,这意味着忽略评分,并考虑使用缓存。效率会高于 must |
should | 条件应该满足。可以通过 minimum_should_match 参数指定应该满足的条件个数。如果 bool 查询包含 should 子句,并且没有 must 和 filter 子句,则默认值为 1,否则默认值为 0 |
must_not | 条件必须不能满足。在 filter context 中执行,这意味着评分被忽略,并考虑使用缓存。因为评分被忽略,所以会返回所有 0 分的文档 |
类似 SQL 的 and,代表必须匹配的条件。
// 创建 bool 查询
boolQuery := elastic.NewBoolQuery()
// 创建查询条件
termQuery := elastic.NewTermQuery("username", "bob")
rangeQuery := elastic.NewRangeQuery("age").Gte(18).Lte(35)
// 设置 bool 查询的 must 条件, 组合了两个子查询
// 搜索用户名为 bob 且年龄在 18~35 岁的用户
boolQuery.Must(termQuery, rangeQuery)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(boolQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query":{
"bool":{
"must":[
{"term":{"username": "bob"}},
{"range":{"age":{"gte":18, "lte":35}}}
]
}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
filter
类似 SQL 的 and,代表必须匹配的条件。不计算匹配分值,且子句被考虑用于缓存。
使用 filter 替代 must 条件,查询用户名为 bob 且年龄在 18~35 岁的用户
// 创建 bool 查询
boolQuery := elastic.NewBoolQuery()
// 创建查询条件
termQuery := elastic.NewTermQuery("username", "bob")
rangeQuery := elastic.NewRangeQuery("age").Gte(18).Lte(35)
// 设置 bool 查询的 filter 条件, 组合了两个子查询
// 搜索用户名为 bob 且年龄在 18~35 岁的用户
boolQuery.Filter(termQuery, rangeQuery)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(boolQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query":{
"bool":{
"filter":[
{"term":{"username": "bob"}},
{"range":{"age":{"gte":18, "lte":35}}}
]
}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
should
类似 SQL 中的 or, 可以通过 minimum_should_match 参数指定应该满足的条件个数。如果 bool 查询包含 should 子句,并且没有 must 和 filter 子句,则默认值为 1,否则默认值为 0。
比如查询用户名为 bob 且年龄为18 或 35 岁的用户。
// 创建 bool 查询
boolQuery := elastic.NewBoolQuery()
// 创建查询条件
termQuery := elastic.NewTermQuery("username", "bob")
termQuery1 := elastic.NewTermQuery("age", 18)
termQuery2 := elastic.NewTermQuery("age", 35)
// 设置 bool 查询的 filter 条件, 组合了两个子查询
// 搜索用户名为 bob 且年龄为 18 或 35 岁的用户
boolQuery.Filter(termQuery, termQuery)
boolQuery.Should(termQuery, termQuery1, termQuery2)
boolQuery.MinimumNumberShouldMatch(1) // 至少满足 should 中的一个条件
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(boolQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query":{
"bool":{
"filter": {"term":{"username": "bob"}},
"should":[
{"term":{"age":18}},
{"term":{"age":35}}
],
"minimum_should_match" : 1
}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
must_not
跟 must 作用相反,表示条件必须不能满足。
比如搜索用户名为 bob 且年龄不为 18 或 35 岁的用户。
// 创建 bool 查询
boolQuery := elastic.NewBoolQuery()
// 创建查询条件
termQuery := elastic.NewTermQuery("username", "bob")
termQuery1 := elastic.NewTermQuery("age", 18)
termQuery2 := elastic.NewTermQuery("age", 35)
// 设置 bool 查询的 filter 条件, 组合了两个子查询
// 搜索用户名为 bob 且年龄不为 18 和 35 岁的用户
boolQuery.Filter(termQuery)
boolQuery.MustNot(termQuery1, termQuery2)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(boolQuery). // 设置查询条件
Sort("create_time", true). // 设置排序字段,根据 create_time 字段升序排序
From(0). // 设置分页参数 - 起始偏移量,从第 0 行记录开始
Size(10). // 设置分页参数 - 每页大小
Do(ctx) // 执行请求
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query":{
"bool":{
"filter": {"term":{"username": "bob"}},
"must_not":[
{"term":{"age":18}},
{"term":{"age":35}}
]
}
},
"sort": [
{"create_time": "asc"}
],
"from": 0,
"size":10
}
8.分页查询
我们也可以根据条件分页查询。
ES 分页搜索一般有三种方案,from + size、search after、scroll api,这三种方案分别有自己的优缺点。
from+size(浅分页)这是 ES 分页中最常用的一种方式,与 MySQL 类似,from 指定起始位置,size 指定返回的文档数。
这种分页方式,在分布式的环境下的深度分页是有性能问题的,一般不建议用这种方式做深度分页,可以用下面将要介绍的两种方式。
理解为什么深度分页是有问题的,假设取的页数较大时(深分页),如请求第20页,Elasticsearch 不得不取出所有分片上的第 1 页到第 20 页的所有文档,并做排序,最终再取出 from 后的 size 条结果作爲最终的返回值。
所以,当索引记录非常非常多(千万或亿),是无法使用 from + size 做深分页的,分页越深则越容易 OOM。即便不 OOM,也很消耗 CPU 和内存资源。
所以 ES 为了避免深分页,不允许使用 from + size 的方式查询 1 万条以后的数据,即 from + size 大于 10000 会报错,不过可以通过 index.max_result_window 参数进行修改。
// GetByQueryPage4ES 分页查询
// param: index 索引; query 查询条件; page 起始页(从 1 开始); size 页大小
func GetByQueryPage4ES(ctx context.Context, index string, query elastic.Query, page, size int) ([]string, error) {
start := (page - 1) * size
res, err := GetESClient().Search(index).Query(query).From(start).Size(size).Do(ctx)
if err != nil {
return nil, err
}
sl := make([]string, 0, res.TotalHits())
for _, hit := range res.Hits.Hits {
sl = append(sl, string(hit.Source))
}
return sl, nil
}
// GetByQueryPageSort4ES 根据条件分页查询 & 指定字段排序
// param: index 索引; query 查询条件; page 起始页(从 1 开始); size 页大小; field 排序字段; ascending 升序
func GetByQueryPageSort4ES(ctx context.Context, index string, query elastic.Query, page, size int, field string,
ascending bool) ([]string, error) {
from := (page - 1) * size
res, err := GetESClient().
Search(index).
Query(query).
Sort(field, ascending).
From(from).
Size(size).
Do(ctx)
if err != nil {
return nil, err
}
sl := make([]string, 0, res.TotalHits())
for _, hit := range res.Hits.Hits {
sl = append(sl, string(hit.Source))
}
return sl, nil
}
比如分页查询年龄 >=18 且按照创建时间降序排序:
query := elastic.NewBoolQuery()
query.Filter(elastic.NewRangeQuery("age").Gte(18))
sl, err := GetByQueryPageSort4ES(context.Background(), index, query, 1, 500, "create_time", false)
对应的 RESTful api 为:
GET /es_index_userinfo/_search
{
"query": {
"bool": {
"filter":[{"range" : {"age" : {"gte" : 18}}}]
}
},
"from": 0,
"size" : 500,
"sort" : [{"create_time":"desc"}]
}
注意:如果想控制返回哪些字段,可以使用 _source 来指定。比如只返回用户名(username)和年龄(age)。
GET /es_index_userinfo/_search
{
"query": {
"bool": {
"filter":[{"range" : {"age" : {"gte" : 18}}}]
}
},
"from": 0,
"size" : 500,
"sort" : [{"create_time":"desc"}],
"_source": ["username", "age"]
}
Go 代码带上_source
的方式。
fsc := elastic.NewFetchSourceContext(true).Include("username", "age")
res, err := GetESClient().
Search(index).
Query(query).
FetchSourceContext(fsc).
Sort(field, ascending).
From(from).
Size(size).
Do(ctx)
scroll api(深分页)
创建一个快照,有新的数据写入以后,无法被查到。每次查询后,输入上一次的 scroll_id。目前官方已经不推荐使用这个 API 了,建议使用 search after。
首先需要获取第一页数据并获取游标 ID,然后便可以根据游标 ID 继续获取下一页数据。如果下一页为空会报 EOF 错误,此时便可知拉取结束了。
比如我们还是要分页获取籍贯为安徽的用户,且按照创建时间降序。
GET es_index_userinfo/_search?scroll=1m
{
"size": 1,
"query": {
"match": {"ancestral": "安徽"}
},
"sort": [
{"create_time": "desc"}
]
}
在返回的数据中,有一个 _scroll_id 字段,下次搜索的时候带上这个数据,并且使用下面的查询语句。
POST _search/scroll
{
"scroll" : "1m",
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFFRpdno4bm9CU3A1TEhvY3ktQjZzAAAAAAKolSoWbm04UWQ5SHlRdDJRRjZaeGFBdjFEQQ=="
}
上面的 scroll 指定搜索上下文保留的时间,1m 代表 1 分钟,还有其他时间可以选择,有 d、h、m、s 等,分别代表天、时、分钟、秒。
搜索上下文有过期自动删除,但如果自己知道什么时候该删,可以自己手动删除,减少资源占用。
DELETE /_search/scroll
{
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAA6UWWVJRTk9TUXFTLUdnU28xVFN6bEM4QQ=="
}
Go 代码示例:
// GetByQueryPageSortScroll4ES 获取第一页数据 & 获取游标ID
// ret: 文档切片, 游标ID, error
func GetByQueryPageSortScroll4ES(ctx context.Context, index string, query elastic.Query, size int, field string,
ascending bool) ([]string, string, error) {
res, err := GetESClient().
Scroll(index).
Query(query).
Sort(field, ascending).
Size(size).
Do(ctx)
if err != nil {
return nil, "", err
}
sl := make([]string, 0, res.TotalHits())
for _, hit := range res.Hits.Hits {
sl = append(sl, string(hit.Source))
}
return sl, res.ScrollId, nil
}
// GetByQScrollID4ES 根据游标 ID 获取下一页
func GetByQScrollID4ES(ctx context.Context, scrollID string) ([]string, error) {
res, err := GetESClient().
Scroll().
ScrollId(scrollID).
Do(ctx)
if err != nil {
return nil, err
}
sl := make([]string, 0, res.TotalHits())
for _, hit := range res.Hits.Hits {
sl = append(sl, string(hit.Source))
}
return sl, nil
}
search after(深分页)
search after 利用实时游标来帮我们解决实时滚动的问题。
第一次搜索时需要指定 sort,并且保证值是唯一的,可以通过加入 _id 保证唯一性。
比如获取籍贯为安徽的用户,且按照创建时间降序。
matchQuery := elastic.NewMatchQuery("ancestral", "安徽")
// 查询第一页(无需指定 SearchAfter)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(matchQuery). // 设置查询条件
Sort("create_time", false). // 按照创建时间降序
Sort("_id", false). // 加入 ID 排序保证 after id 唯一
Size(1000). // 设置分页参数
Do(ctx)
// 查询第 n 页(n > 1,需要指定 SearchAfter)
searchResult, err := GetESClient().Search().
Index("es_index_userinfo"). // 设置索引名
Query(matchQuery). // 设置查询条件
Sort("create_time", false). // 按照创建时间降序
Sort("_id", false). // 加入 ID 排序保证 after id 唯一
SearchAfter(lastCreateTime, id). // 上页最后一条的创建时间和 ID
Size(1000). // 设置分页参数
Do(ctx)
对应 RESTful api 的示例。
GET es_index_userinfo/_search
{
"size": 1,
"query": {
"match": {"ancestral": "安徽"}
},
"sort": [
{"create_time": "desc"},
{"_id": "desc"}
]
}
在返回的结果中,最后一个文档有类似下面的数据,由于我们排序用的是两个字段,返回的是两个值。
"sort" : [
1627522828,
"2"
]
第二次搜索,带上这个 sort 信息即可,如下:
GET es_index_userinfo/_search
{
"size": 1,
"query": {
"match": {"ancestral": "安徽"}
},
"sort": [
{"create_time": "desc"},
{"_id": "desc"}
],
"search_after": [
1627522828,
"2"
]
}
小结
from + size 的优点是简单,缺点是在深度分页的场景下系统开销比较大。
search after 可以实时高效的进行分页查询,但是它只能做下一页这样的查询场景,不能随机的指定页数查询。
scroll api 方案也很高效,但是它基于快照,不能用在实时性高的业务场景,且官方已不建议使用。
9.查询文档是否存在借助 ExistsService 使用 HEAD 检查文档是否存在判断。
如果文档存在, Elasticsearch 将返回一个 200 ok 的状态码,若文档不存在, Elasticsearch 将返回一个 404 Not Found 的状态码。
9.1 根据 ID 判断文档是否存在// IsDocExists 某条记录是否存在
func IsDocExists(ctx context.Context, id, index string) (bool, error) {
return GetESClient().Exists().Index(index).Id(id).Do(ctx)
}
RESTful api 示例:
head es_index_userinfo/_doc/1
返回:
200 - OK
9.2 查询符合条件的文档数量
可以借助 CountService 查询符合条件的文档数量,进而判断文档是否存在。
比如查询年龄>=18
的用户数量。
// 创建 range 查询条件
rangeQuery := elastic.NewRangeQuery("age").Gte(18)
cnt, err := GetESClient().
Count("es_index_userinfo").
Query(rangeQuery).
Do(ctx)
RESTful api 示例:
GET es_index_userinfo/_count
{
"query": {
"range": {
"age": {"gte" : 18}
}
}
}
10.获取文档数量
上一节已经说了可以借助 CountService 查询符合条件的文档数量,如果想查询 index 下的所有文档呢?
很简单,不指定条件即可。
// GetIndexDocNum 获取索引文档总数
func GetIndexDocNum(ctx context.Context, index string) (int64, error) {
return GetESClient().Count(index).Do(ctx)
}
RESTful API 示例:
GET es_index_userinfo/_count
# 示例结果
{
"count" : 337,
"_shards" : {
"total" : 20,
"successful" : 20,
"skipped" : 0,
"failed" : 0
}
}
12.脚本查询
有时候,我们需要通过脚本来设置复杂的查询条件。
注意:
脚本查询属于慢查询,在对性能要求严格的场景下谨慎使用;如果 search.allow_expensive_queries 被设置为 false(缺省为 true),则无法进行脚本查询。 12.1 判断数组长度ES 中每一个字段都是数组,我们无需对字段做额外的设置,便可以将其当作数组来使用。
有时我们需要根据某个字段的元素个数,即数组长度来查询,此时便需要借助于脚本来完成。比如查询 phone 字段的长度为 1 个且等于某个值。
POST /es_index_userinfo/_search
{
"query": {
"bool":{
"filter": [
{"term":{"phone":18819064334}},
{"script": {"script": "doc['phone'].length == 1"}}
]
}
}
}
对应的 Golang 条件设置如下:
boolQuery := elastic.NewBoolQuery().
Filter(elastic.NewTermQuery("phone", 18819064334)).
Filter(elastic.NewScriptQuery(elastic.NewScript("doc['phone'].length == 1")))
如果需要向脚本传参,我们在脚本中设置相关的参数即可。
POST /es_index_userinfo/_search
{
"query": {
"bool":{
"filter": [
{"term":{"phone":18819064334}},
{
"script": {
"script": {
"source": "doc['phone'].length == params.length",
"params": {"length": 1}
}
}
}
]
}
}
}
对应 Golang 条件设置如下:
boolQuery := elastic.NewBoolQuery().
Filter(elastic.NewTermQuery("phone", 18819064334)).
Filter(elastic.NewScriptQuery(elastic.NewScript("doc['phone'].length == 1").Params(
map[string]interface{}{
"length": 1,
},
)))
13.遍历查询结果
获取到查询结果后,我们可以借助 olivere/elastic 提供的工具函数 func (*SearchResult) Each 完成对查询结果的遍历。下面是一个示例。
假设 ES 中的文档对应的 Go struct 如下。
type UserInfo struct {
Id uint64 `json:"id,omitempty"`
Age int32 `json:"age,omitempty"`
Username string `json:"username,omitempty"`
Nickname string `json:"nickname,omitempty"`
Identity string `json:"identity,omitempty"`
Phone uint64 `json:"phone,omitempty"`
Ancestral string `json:"ancestral,omitempty"`
UpdateTime int64 `json:"update_time,omitempty"`
CreateTime int64 `json:"create_time,omitempty"`
}
遍历取出查询到的文档。
for _, v := range res.Each(reflect.TypeOf(UserInfo{})) {
u := v.(UserInfo)
...
}
参考文献
github/elastic/elasticsearch
github/olivere/elastic/v7
pkg.go.dev/github.com/olivere/elastic/v7
掘金.Elasticsearch 分页查询
golang elasticsearch 查询教程
CSDN.ES中如何对text字段进行精确匹配
知乎.一文搞懂match、match_phrase与match_phrase_prefix的检索过程
Elasticsearch Guide [8.2] » REST APIs » Document APIs » Multi get (mget) API
Elasticsearch Guide [7.15] » Query DSL » Full text queries » Match query
Elasticsearch Guide [7.15] » Query DSL » Full text queries » Match phrase query
Elasticsearch Guide [7.15] » Query DSL » Full text queries » Match phrase prefix query
Elasticsearch Guide [7.15] » REST APIs » Search APIs » Count API
Elasticsearch Guide [8.1] » Query DSL » Term-level queries
Elasticsearch Guide [8.1] » Query DSL » Full text queries
Elasticsearch Guide [8.1] » Query DSL » Specialized queries » Script query
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)