golang elasticsearch入门教程

本教程从go语言角度讲解如何对elasticsearch进行增删改查。

目前golang操作elasticsearch的第三方包中最流行的是:

https://github.com/olivere/elastic

本教程也是基于elastic开发包进行讲解。

版本说明

golang的elastic开发包和elasticsearch版本有一些对应关系,在开发前需要注意下,必须选择正确的版本,下面是golang elastic开发包和elasticsearch版本关系表:

Elasticsearch versionGo Elastic versiongo开发包地址
7.x 7.0 github.com/olivere/elastic/v7
6.x 6.0 github.com/olivere/elastic
5.x 5.0 gopkg.in/olivere/elastic.v5

例如:ES版本是7.0以后的版本,就使用github.com/olivere/elastic/v7这个包地址。

安装依赖包

本教程ES使用的是7.0以后的版本,因此安装GO的依赖包如下

go get github.com/olivere/elastic/v7

提示:如果使用goland作为ide,直接导入 import "github.com/olivere/elastic/v7" 包,goland会自动安装依赖包。

创建ES客户端

在操作ES之前需要创建一个client,用于操作ES,在创建client的时候需要提供ES连接参数。

package main

import "fmt"
import "github.com/olivere/elastic/v7"

func main() {
      // 创建ES client用于后续操作ES
client, err := elastic.NewClient(
              // 设置ES服务地址,支持多个地址
elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"),
              // 设置基于http base auth验证的账号和密码
elastic.SetBasicAuth("user", "secret"))
if err != nil {
// Handle error
fmt.Printf("连接失败: %v\n", err)
} else {
fmt.Println("连接成功")
}
}

创建索引

package main

import (
"context"
"fmt"
      "github.com/olivere/elastic/v7"
)

// 索引mapping定义,这里仿微博消息结构定义
const mapping = `
{
"mappings": {
  "properties": {
    "user": {
      "type": "keyword"
    },
    "message": {
      "type": "text"
    },
    "image": {
      "type": "keyword"
    },
    "created": {
      "type": "date"
    },
    "tags": {
      "type": "keyword"
    },
    "location": {
      "type": "geo_point"
    },
    "suggest_field": {
      "type": "completion"
    }
  }
}
}`

func main() {
      // 创建client
client, err := elastic.NewClient(
elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"),
elastic.SetBasicAuth("user", "secret"))
if err != nil {
// Handle error
fmt.Printf("连接失败: %v\n", err)
} else {
fmt.Println("连接成功")
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 首先检测下weibo索引是否存在
exists, err := client.IndexExists("weibo").Do(ctx)
if err != nil {
// Handle error
panic(err)
}
if !exists {
// weibo索引不存在,则创建一个
_, err := client.CreateIndex("weibo").BodyString(mapping).Do(ctx)
if err != nil {
// Handle error
panic(err)
}
}
}

提示:后续代码不再提重复提供完整的代码,直接引用client对象,则假定你已经完成包的加载和初始化client对象。

插入一条数据

先定义微博的struct, 跟前面创建的weibo索引结构一一对应。

type Weibo struct {
User     string               `json:"user"` // 用户
Message string               `json:"message"` // 微博内容
Retweets int                   `json:"retweets"` // 转发数
Image   string               `json:"image,omitempty"` // 图片
Created time.Time             `json:"created,omitempty"` // 创建时间
Tags     []string             `json:"tags,omitempty"` // 标签
Location string               `json:"location,omitempty"` //位置
Suggest *elastic.SuggestField `json:"suggest_field,omitempty"`
}

上面struct定义的时候,都定义了json结构,因为ES请求使用的是json格式,在发送ES请求的时候,会自动转换成json格式。

使用struct结构插入一条ES文档数据,

// 创建创建一条微博
msg1 := Weibo{User: "olivere", Message: "打酱油的一天", Retweets: 0}

// 使用client创建一个新的文档
put1, err := client.Index().
Index("weibo"). // 设置索引名称
Id("1"). // 设置文档id
BodyJson(msg1). // 指定前面声明的微博内容
Do(ctx) // 执行请求,需要传入一个上下文对象
if err != nil {
// Handle error
panic(err)
}

fmt.Printf("文档Id %s, 索引名 %s\n", put1.Id, put1.Index)

查询数据

// 根据id查询文档
get1, err := client.Get().
Index("weibo"). // 指定索引名
Id("1"). // 设置文档id
Do(ctx) // 执行请求
if err != nil {
// Handle error
panic(err)
}
if get1.Found {
fmt.Printf("文档id=%s 版本号=%d 索引名=%s\n", get1.Id, get1.Version, get1.Index)
}

# 手动将文档内容转换成go struct对象
msg2 := Weibo{}
// 提取文档内容,原始类型是json数据
data, _ := get1.Source.MarshalJSON()
// 将json转成struct结果
json.Unmarshal(data, &msg2)
// 打印结果
fmt.Println(msg2.Message)

更新数据

根据文档id更新内容

_, err := client.Update().
Index("weibo"). // 设置索引名
Id("1"). // 文档id
Doc(map[string]interface{}{"retweets": 0}). // 更新retweets=0,支持传入键值结构
Do(ctx) // 执行ES查询
if err != nil {
  // Handle error
  panic(err)
}

删除数据

// 根据id删除一条数据
_, err := client.Delete().
Index("weibo").
Id("1").
Do(ctx)
if err != nil {
// Handle error
panic(err)
}

提示:更多细节请参考后续章节

golang elasticsearch连接配置

本节介绍golang elastic client参数详解,主要包括:

  • elasticsearch连接地址

  • elasticsearch账号/密码

  • 监控检查

  • 失败重试次数

  • gzip设置

client, err := elastic.NewClient(
// elasticsearch 服务地址,多个服务地址使用逗号分隔
elastic.SetURL("http://10.0.1.1:9200", "http://10.0.1.2:9200"),
// 基于http base auth验证机制的账号和密码
elastic.SetBasicAuth("user", "secret"),
// 启用gzip压缩
elastic.SetGzip(true),
// 设置监控检查时间间隔
elastic.SetHealthcheckInterval(10*time.Second),
// 设置请求失败最大重试次数
elastic.SetMaxRetries(5),
// 设置错误日志输出
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)),
// 设置info日志输出
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)))
if err != nil {
  // Handle error
  panic(err)
}
_ = client

golang elasticsearch 文档操作(CRUD)

本节主要介绍go语言对Elasticsearch文档的基础操作:创建、查询、更新、删除。

为了方便演示文档的CRUD操作,我们先定义索引的struct结构

// 定义一个文章索引结构,用来存储文章内容
type Article struct {
Title   string   // 文章标题
Content string   // 文章内容
Author string   // 作者
Created time.Time // 发布时间
}

添加文档

 package main

import (
"context"
"encoding/json"
"fmt"
"github.com/olivere/elastic/v7"
"log"
"os"
"time"
)

type Article struct {
Title   string   // 文章标题
Content string   // 文章内容
Author string   // 作者
Created time.Time // 发布时间
}


func main() {
      // 创建client连接ES
client, err := elastic.NewClient(
// elasticsearch 服务地址,多个服务地址使用逗号分隔
elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"),
// 基于http base auth验证机制的账号和密码
elastic.SetBasicAuth("user", "secret"),
// 启用gzip压缩
elastic.SetGzip(true),
// 设置监控检查时间间隔
elastic.SetHealthcheckInterval(10*time.Second),
// 设置请求失败最大重试次数
elastic.SetMaxRetries(5),
// 设置错误日志输出
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)),
// 设置info日志输出
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)))

if err != nil {
// Handle error
fmt.Printf("连接失败: %v\n", err)
} else {
fmt.Println("连接成功")
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 定义一篇博客
blog := Article{Title:"golang es教程", Content:"go如何操作ES", Author:"tizi", Created:time.Now()}

// 使用client创建一个新的文档
put1, err := client.Index().
Index("blogs"). // 设置索引名称
Id("1"). // 设置文档id
BodyJson(blog). // 指定前面声明struct对象
Do(ctx) // 执行请求,需要传入一个上下文对象
if err != nil {
// Handle error
panic(err)
}

fmt.Printf("文档Id %s, 索引名 %s\n", put1.Id, put1.Index)
}

提示:后续的章节不再重复给出完整的代码,仅给出关键代码片段

查询文档

根据文档ID,查询文档

// 根据id查询文档
get1, err := client.Get().
Index("blogs"). // 指定索引名
Id("1"). // 设置文档id
Do(ctx) // 执行请求
if err != nil {
// Handle error
panic(err)
}
if get1.Found {
fmt.Printf("文档id=%s 版本号=%d 索引名=%s\n", get1.Id, get1.Version, get1.Index)
}

# 手动将文档内容转换成go struct对象
msg2 := Article{}
// 提取文档内容,原始类型是json数据
data, _ := get1.Source.MarshalJSON()
// 将json转成struct结果
json.Unmarshal(data, &msg2)
// 打印结果
fmt.Println(msg2.Title)

批量查询文档

通过多个Id批量查询文档,对应ES的multi get

// 查询id等于1,2,3的博客内容
result, err := client.MultiGet().
Add(elastic.NewMultiGetItem(). // 通过NewMultiGetItem配置查询条件
Index("blogs"). // 设置索引名
Id("1")). // 设置文档id
Add(elastic.NewMultiGetItem().Index("blogs").Id("2")).
Add(elastic.NewMultiGetItem().Index("blogs").Id("3")).
Do(ctx) // 执行请求

if err != nil {
panic(err)
}

// 遍历文档
for _, doc := range result.Docs {
// 转换成struct对象
var content Article
tmp, _ := doc.Source.MarshalJSON()
err := json.Unmarshal(tmp, &content)
if err != nil {
panic(err)
}

fmt.Println(content.Title)
}

更新文档

根据id更新文档

_, err := client.Update().
Index("blogs"). // 设置索引名
Id("1"). // 文档id
Doc(map[string]interface{}{"Title": "新的文章标题"}). // 更新Title="新的文章标题",支持传入键值结构
Do(ctx) // 执行ES查询
if err != nil {
  // Handle error
  panic(err)
}

根据条件更新文档

支持批量更新文档内容

_, err = client.UpdateByQuery("blogs").
              // 设置查询条件,这里设置Author=tizi
Query(elastic.NewTermQuery("Author", "tizi")).
              // 通过脚本更新内容,将Title字段改为1111111
Script(elastic.NewScript( "ctx._source['Title']='1111111'")).
              // 如果文档版本冲突继续执行
ProceedOnVersionConflict().
Do(ctx)

提示: 复杂查询条件,请参考go es查询用法

删除文档

// 根据id删除一条数据
_, err := client.Delete().
Index("blogs").
Id("1"). // 文档id
Do(ctx)
if err != nil {
// Handle error
panic(err)
}

根据条件删除文档

_, _ = client.DeleteByQuery("blogs"). // 设置索引名
  // 设置查询条件为: Author = tizi
Query(elastic.NewTermQuery("Author", "tizi")).
// 文档冲突也继续删除
ProceedOnVersionConflict().
Do(ctx)

提示: 复杂查询条件,请参考go es查询用法

golang elasticsearch 查询教程

elasticsearch的查询语法比较丰富,下面分别介绍golang 的各种查询用法。

如果对ES的查询语法和概念不了解,请阅读:ES教程

1.精确匹配单个字段

elasticsearch的term查询,下面给出完整的代码

package main

import (
"context"
"fmt"
"github.com/olivere/elastic/v7"
"log"
"os"
"reflect"
"time"
)

type Article struct {
Title   string   // 文章标题
Content string   // 文章内容
Author string   // 作者
Created time.Time // 发布时间
}


func main() {
      // 创建Client, 连接ES
client, err := elastic.NewClient(
// elasticsearch 服务地址,多个服务地址使用逗号分隔
elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"),
// 基于http base auth验证机制的账号和密码
elastic.SetBasicAuth("user", "secret"),
// 启用gzip压缩
elastic.SetGzip(true),
// 设置监控检查时间间隔
elastic.SetHealthcheckInterval(10*time.Second),
// 设置请求失败最大重试次数
elastic.SetMaxRetries(5),
// 设置错误日志输出
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)),
// 设置info日志输出
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)))

if err != nil {
// Handle error
fmt.Printf("连接失败: %v\n", err)
} else {
fmt.Println("连接成功")
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 创建term查询条件,用于精确查询
termQuery := elastic.NewTermQuery("Author", "tizi")

searchResult, err := client.Search().
Index("blogs").   // 设置索引名
Query(termQuery).   // 设置查询条件
Sort("Created", true). // 设置排序字段,根据Created字段升序排序,第二个参数false表示逆序
From(0). // 设置分页参数 - 起始偏移量,从第0行记录开始
Size(10).   // 设置分页参数 - 每页大小
Pretty(true).       // 查询结果返回可读性较好的JSON格式
Do(ctx)             // 执行请求

if err != nil {
// Handle error
panic(err)
}

fmt.Printf("查询消耗时间 %d ms, 结果总数: %d\n", searchResult.TookInMillis, searchResult.TotalHits())


if searchResult.TotalHits() > 0 {
// 查询结果不为空,则遍历结果
var b1 Article
// 通过Each方法,将es结果的json结构转换成struct对象
for _, item := range searchResult.Each(reflect.TypeOf(b1)) {
// 转换成Article对象
if t, ok := item.(Article); ok {
fmt.Println(t.Title)
}
}
}
}

提示:后续章节,仅给出关键代码片段,其他代码结构参考本节即可

2.通过terms实现SQL的in查询

通过terms查询语法实现,多值查询效果

例子:

// 创建terms查询条件
termsQuery := elastic.NewTermsQuery("Author", "tizi", "tizi365")

searchResult, err := client.Search().
Index("blogs").   // 设置索引名
Query(termsQuery).   // 设置查询条件
Sort("Created", true). // 设置排序字段,根据Created字段升序排序,第二个参数false表示逆序
From(0). // 设置分页参数 - 起始偏移量,从第0行记录开始
Size(10).   // 设置分页参数 - 每页大小
Do(ctx)             // 执行请求

3.匹配单个字段

某个字段使用全文搜索,也就是ES的match语法

例子:

// 创建match查询条件
matchQuery := elastic.NewMatchQuery("Title", "golang es教程")

searchResult, err := client.Search().
Index("blogs").   // 设置索引名
Query(matchQuery).   // 设置查询条件
Sort("Created", true). // 设置排序字段,根据Created字段升序排序,第二个参数false表示逆序
From(0). // 设置分页参数 - 起始偏移量,从第0行记录开始
Size(10).   // 设置分页参数 - 每页大小
Do(ctx)

4.范围查询

实现类似Created > '2020-07-20' and Created < '2020-07-22'的范围查询条件

创建查询表达式例子:

// 例1 等价表达式: Created > "2020-07-20" and Created < "2020-07-29"
rangeQuery := elastic.NewRangeQuery("Created").
Gt("2020-07-20").
Lt("2020-07-29")

// 例2 等价表达式: id >= 1 and id < 10
rangeQuery := elastic.NewRangeQuery("id").
Gte(1).
Lte(10)

5.bool组合查询

bool组合查询,实际上就是组合了前面的查询条件,然后通过类似SQL语句的and和or将查询条件组合起来,不熟悉ES查询语法,请参考ES教程

5.1. must条件

类似SQL的and,代表必须匹配的条件。

// 创建bool查询
boolQuery := elastic.NewBoolQuery().Must()

// 创建term查询
termQuery := elastic.NewTermQuery("Author", "tizi")
matchQuery := elastic.NewMatchQuery("Title", "golang es教程")

// 设置bool查询的must条件, 组合了两个子查询
// 表示搜索匹配Author=tizi且Title匹配"golang es教程"的文档
boolQuery.Must(termQuery, matchQuery)

searchResult, err := client.Search().
Index("blogs").   // 设置索引名
Query(boolQuery).   // 设置查询条件
Sort("Created", true). // 设置排序字段,根据Created字段升序排序,第二个参数false表示逆序
From(0). // 设置分页参数 - 起始偏移量,从第0行记录开始
Size(10).   // 设置分页参数 - 每页大小
Do(ctx)             // 执行请求

5.2. must_not条件

跟must的作用相反,用法和must类似

// 创建bool查询
boolQuery := elastic.NewBoolQuery().Must()

// 创建term查询
termQuery := elastic.NewTermQuery("Author", "tizi")

// 设置bool查询的must not条件
boolQuery.MustNot(termQuery)

5.2. should条件

类似SQL中的 or, 只要匹配其中一个条件即可

// 创建bool查询
boolQuery := elastic.NewBoolQuery().Must()

// 创建term查询
termQuery := elastic.NewTermQuery("Author", "tizi")
matchQuery := elastic.NewMatchQuery("Title", "golang es教程")

// 设置bool查询的should条件, 组合了两个子查询
// 表示搜索Author=tizi或者Title匹配"golang es教程"的文档
boolQuery.Should(termQuery, matchQuery)

提示:go的elastic库,组合bool语句的用法,跟ES bool语法类似,可以互相嵌套查询语句。

聚合分析

golang elasticsearch 聚合分析(Aggregation)

elasticsearch聚合分析的概念和语法可以参考:ES聚合分析

这里主要介绍golang elasticsearch聚合分析的用法。

我们都知道ES聚合分析主要包括:

  • 指标聚合

  • 桶聚合

这两种聚合可以嵌套混合使用,桶聚合通常用于对数据分组,然后分组内的数据可以使用指标聚合汇总数据。

下面看一个综合的聚合分析的例子:

// 创建ES client
client, err := elastic.NewClient()
if err != nil {
  // Handle error
  panic(err)
}

// 创建一个terms聚合,根据user字段分组,同时设置桶排序条件为按计数倒序排序,并且返回前10条桶数据
timeline := elastic.NewTermsAggregation().Field("user").Size(10).OrderByCountDesc()
// 创建Date histogram聚合,根据created时间字段分组,按年分组
histogram := elastic.NewDateHistogramAggregation().Field("created").CalendarInterval("year")

// 设置timeline的嵌套聚合条件,整体意思就是:首先按user字段分组,然后分组数据内,再次根据created时间字段按年分组,进行了两次分组。
timeline = timeline.SubAggregation("history", histogram)

// 执行ES查询
searchResult, err := client.Search().
  Index("twitter").                 // 设置索引名
  Query(elastic.NewMatchAllQuery()). // 设置查询条件
  Aggregation("timeline", timeline). // 设置聚合条件,并为聚合条件设置一个名字
  Pretty(true).                     // 返回可读的json格式
  Do(context.Background())           // 执行
if err != nil {
  // Handle error
  panic(err)
}

// 遍历ES查询结果,因为我们首先使用的是terms聚合条件,
// 所以查询结果先使用Terms函数和聚合条件的名字读取结果。
agg, found := searchResult.Aggregations.Terms("timeline")
if !found {
  // 没有查询到terms聚合结果
  log.Fatalf("we should have a terms aggregation called %q", "timeline")
}

// 遍历桶数据
for _, userBucket := range agg.Buckets {
  // 每一个桶都有一个key值,其实就是分组的值,可以理解为SQL的group by值
  user := userBucket.Key

  // 查询嵌套聚合查询的数据
  // 因为我们使用的是Date histogram聚合,所以需要使用DateHistogram函数和聚合名字获取结果
  histogram, found := userBucket.DateHistogram("history")
  if found {
      // 如果找到Date histogram聚合结果,则遍历桶数据
      for _, year := range histogram.Buckets {
          var key string
          if s := year.KeyAsString; s != nil {
              // 因为返回的是指针类型,这里做一下取值运算
              key = *s
          }
          // 打印结果
          fmt.Printf("user %q has %d tweets in %q\n", user, year.DocCount, key)
      }
  }
}

后面的章节再分别介绍指标聚合和桶聚合的详细写法。

golang elasticsearch指标聚合(metrics)

ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用,下面介绍golang如何使用ES的指标聚合。

不了解ES指标聚合相关知识,先看一下Elasticsearch 指标聚合教程

1. Value Count

值聚合,主要用于统计文档总数,类似SQL的count函数。

package main

import (
"context"
"fmt"
"github.com/olivere/elastic/v7"
"time"
)

func main() {
// 创建ES client
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 创建Value Count指标聚合
aggs := elastic.NewValueCountAggregation().
Field("order_id") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("total", aggs). // 设置聚合条件,并为聚合条件设置一个名字, 支持添加多个聚合条件,命名不一样即可。
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用ValueCount函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.ValueCount("total")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}
}

提示:go elastic库,所有聚合分析结果都是通过对应的函数获取结果,例如前面的例子,Value Count聚合结果,通过ValueCount函数获取结果,后面继续介绍其他指标聚合的用法。

2.Cardinality

基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。

提示:基数聚合是一种近似算法,统计的结果会有一定误差,不过性能很好。

// 创建Cardinality指标聚合
aggs := elastic.NewCardinalityAggregation().
Field("order_id") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("total", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Cardinality函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Cardinality("total")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}

3.Avg

求平均值

// 创建Avg指标聚合
aggs := elastic.NewAvgAggregation().
Field("price") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("avg_price", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Avg函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Avg("avg_price")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}

4.Sum

求和计算

// 创建Sum指标聚合
aggs := elastic.NewSumAggregation().
Field("price") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("total_price", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Sum函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Sum("total_price")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}

5.Max

求最大值

// 创建Sum指标聚合
aggs := elastic.NewMaxAggregation().
Field("price") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("max_price", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Max函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Max("max_price")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}

6.Min

求最小值

// 创建Min指标聚合
aggs := elastic.NewMinAggregation().
Field("price") // 设置统计字段

searchResult, err := client.Search().
Index("kibana_sample_data_flights"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("min_price", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Min函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Min("min_price")
if found {
// 打印结果,注意:这里使用的是取值运算符
fmt.Println(*agg.Value)
}

golang elasticsearch 桶聚合(bucket)

Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。

不了解Elasticsearch桶聚合概念,可以先学习下Elasticsearch桶聚合教程

下面分别介绍golang elasticsearch桶聚合的写法

1.Terms聚合

package main

import (
"context"
"fmt"
"github.com/olivere/elastic/v7"
"log"
)

func main() {
// 创建ES client
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 创建Terms桶聚合
aggs := elastic.NewTermsAggregation().
Field("shop_id") // 根据shop_id字段值,对数据进行分组

searchResult, err := client.Search().
Index("shops"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("shop", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Terms函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Terms("shop")
if !found {
log.Fatal("没有找到聚合数据")
}

// 遍历桶数据
for _, bucket := range agg.Buckets {
// 每一个桶都有一个key值,其实就是分组的值,可以理解为SQL的group by值
bucketValue := bucket.Key

// 打印结果, 默认桶聚合查询,都是统计文档总数
fmt.Printf("bucket = %q 文档总数 = %d\n", bucketValue, bucket.DocCount)
}
}

2.Histogram聚合

// 创建Histogram桶聚合
aggs := elastic.NewHistogramAggregation().
Field("price"). // 根据price字段值,对数据进行分组
Interval(50) // 分桶的间隔为50,意思就是price字段值按50间隔分组

searchResult, err := client.Search().
Index("order"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("prices", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Histogram函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Histogram("prices")
if !found {
log.Fatal("没有找到聚合数据")
}

// 遍历桶数据
for _, bucket := range agg.Buckets {
// 每一个桶都有一个key值,其实就是分组的值,可以理解为SQL的group by值
bucketValue := bucket.Key

// 打印结果, 默认桶聚合查询,都是统计文档总数
fmt.Printf("bucket = %q 文档总数 = %d\n", bucketValue, bucket.DocCount)
}

3.Date histogram聚合

// 创DateHistogram桶聚合
aggs := elastic.NewDateHistogramAggregation().
Field("date"). // 根据date字段值,对数据进行分组
// 分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
CalendarInterval("month").
// 设置返回结果中桶key的时间格式
Format("yyyy-MM-dd")

searchResult, err := client.Search().
Index("order"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("sales_over_time", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用DateHistogram函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.DateHistogram("sales_over_time")
if !found {
log.Fatal("没有找到聚合数据")
}

// 遍历桶数据
for _, bucket := range agg.Buckets {
// 每一个桶都有一个key值,其实就是分组的值,可以理解为SQL的group by值
bucketValue := bucket.Key

// 打印结果, 默认桶聚合查询,都是统计文档总数
fmt.Printf("bucket = %q 文档总数 = %d\n", bucketValue, bucket.DocCount)
}

4.Range聚合

// 创Range桶聚合
aggs := elastic.NewRangeAggregation().
Field("price"). // 根据price字段分桶
AddUnboundedFrom(100). // 范围配置, 0 - 100
AddRange(100.0, 200.0). // 范围配置, 100 - 200
AddUnboundedTo(200.0) // 范围配置,> 200的值

searchResult, err := client.Search().
Index("order"). // 设置索引名
Query(elastic.NewMatchAllQuery()). // 设置查询条件
Aggregation("price_ranges", aggs). // 设置聚合条件,并为聚合条件设置一个名字
Size(0). // 设置分页参数 - 每页大小,设置为0代表不返回搜索结果,仅返回聚合分析结果
Do(ctx) // 执行请求

if err != nil {
// Handle error
panic(err)
}

// 使用Range函数和前面定义的聚合条件名称,查询结果
agg, found := searchResult.Aggregations.Range("price_ranges")
if !found {
log.Fatal("没有找到聚合数据")
}

// 遍历桶数据
for _, bucket := range agg.Buckets {
// 每一个桶都有一个key值,其实就是分组的值,可以理解为SQL的group by值
bucketValue := bucket.Key

// 打印结果, 默认桶聚合查询,都是统计文档总数
fmt.Printf("bucket = %q 文档总数 = %d\n", bucketValue, bucket.DocCount)
}

5.嵌套聚合的用法

任意聚合类型都支持嵌套,桶聚合可以嵌套桶聚合,也可以嵌套指标聚合。

例子:

// 创terms桶聚合
aggs := elastic.NewTermsAggregation().Field("shop_id")
// 创建Sum指标聚合
sumAggs := elastic.NewSumAggregation().Field("price")
// terms聚合嵌套指标聚合
aggs.SubAggregation("total_price", sumAggs)

提示:golang elasticsearch的用法,本质上还是对elasticsearch接口的封装,所以用法跟elasticsearch的语法完全一致。

其他

golang elasticsearch 索引操作API

创建索引

// 创建ES client
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}

// 执行ES请求需要提供一个上下文对象
ctx := context.Background()

// 索引mapping定义,这里仿微博消息结构定义
const mapping = `
{
"mappings": {
  "properties": {
    "user": {
      "type": "keyword"
    },
    "message": {
      "type": "text"
    },
    "image": {
      "type": "keyword"
    },
    "created": {
      "type": "date"
    },
    "tags": {
      "type": "keyword"
    },
    "location": {
      "type": "geo_point"
    },
    "suggest_field": {
      "type": "completion"
    }
  }
}
}`
// 创建索引
_, err = client.CreateIndex("weibo").BodyString(mapping).Do(ctx)
if err != nil {
// Handle error
panic(err)
}

删除索引

删除blog索引

client.DeleteIndex("blog").Do(ctx)

检测索引是否存在

// 检测下weibo索引是否存在
exists, err := client.IndexExists("weibo").Do(ctx)
if err != nil {
// Handle error
panic(err)
}
 
posted on 2022-08-10 14:02  root-123  阅读(6015)  评论(0编辑  收藏  举报