golang elasticsearch入门教程

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

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

https://github.com/olivere/elastic

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

版本说明

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

Elasticsearch version

Go Elastic version

go开发包地址

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)
}