elasticsearch 详解

elasticsearch 详解

elasticsearch简单说明

1.该文档主要来说明elasticsearch的常用方法,以及一些注意事项。

2.elasticsearch 是一种非关系型数据库,主要解决一些查询问题,一般情况下单标查询速度非常快,也会介绍多表查询的方法,由于elasticsearch升级到6x后取消了type这个功能,所以我还是比较喜欢用5x,我现在用的版本是5.6.7。如果不使用多表关系,建议大家用最新版本。

3.elasticsearch使用json格式进行交互,结果清晰。

4.elasticsearch底层基于lucene,可以先了解下lucene的原理,方便学习。

elasticsearch使用方法

概念

在使用elasticsearch前需要了解一些概念

  • cluster,集群概念。

  • index,elasticsearch的索引,类似数据库中库。

  • type,elasticsearch的类型,类似于数据库,但是在elasticsearch6.0以后将次概念移除,只允许一个type对应一个index。
  • document,elasticsearch真真存储的信息,以json格式存储。
  • _id,为该条文档的id,只能唯一,如果重复则会修改文档值
  • took,查询相应时间,以毫秒为单位。
  • timed_out,查询结果是否超时。
  • _shards,查询的分片信息
  • hits,查询的结果集;total,查询出结果的相应条数。
  • max_score,查询结果的得分情况,这个得分将会直接影响到后续的查询结果,如果想要精确查询,需要使用查询函数将这个值设置为1,后续会重点说明这个问题。
  • aggregations聚合结果集

倒排索引

Elasticsearch 使用一种称为 *倒排索引* 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

假设我们有两个文档

1.elasticsearch is very good

2.sun is very big

倒排序结果为:

Term Doc1 Doc2
elasticsearch ✔️
is ✔️ ✔️
very ✔️ ✔️
good ✔️
sun ✔️
big ✔️

此时搜索 very good,结果为:

Term Doc1 Doc2
very ✔️ ✔️
good ✔️

Doc1的total为2,Doc2的total为1,此结果也影响到了搜索后的得分情况。

创建索引

在创建索引前需要引入一个新概念mapping,mapping是elasticsearch的映射关系,就好比sql创建表时需要指定数据类型,elasticsearch5.x版本中常用数据类型如下

String字符串类型

  • text,适合文本、段落类型,如果需要聚合不建议采用该类型。
  • keyword,适合一般关键字段,该类型可以直接聚合。

Number类型

  • byte
  • short
  • integer
  • long
  • float
  • double

Boolean类型

  • boolean 布尔类型

日期类型

  • date 日期类型

创建mapping

以创建登录表为例,索引为login,type为log,有三个字段,user_id、user_name、login_date,其中login_date为时间字段,其余为可聚合的string类型。创建索引的http请求为PUT请求。

curl -XPUT 'localhost:9200/login' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "log": {
      "properties": {
           "user_id": { "type": "keyword" },
           "user_name": { "type": "keyword" },
           "login_date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
      }
    }
  }
}'

#返回结果如下表示创建成功,注意如果有该索引则会创建失败
{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "login"
}

修改副本问题

当单节点机器部署的时候,创建完成索引后会出现Unassigned的节点,原因是创建索引时如果不指定副本,默认会增加一个副本,但是是未分片状态,集群健康值: yellow,出现这种情况数据量少的时候不会感觉什么问题,但是一旦数据量特别大的时候会感觉明显性能降低。方法是将副本数量改成0

PUT myindex/_settings
{
    "number_of_replicas": 0
}

导入数据

以post的方式导入数据

curl -XPOST 'localhost:9200/login/log/1' -H 'Content-Type: application/json' -d'
{
  "user_id":"u001",
  "user_name":"Maker",
  "login_date":"2018-01-01 00:00:00"
}'

#返回结果如下表示创建成功

{
    "_index": "login",
    "_type": "log",
    "_id": "AWH0vmYkJPBV9CR0BWlg",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "created": true
}

使用_bulk参数,批量导入大量离线数据

1.首先先创建json文件

cat login.json

{ "index" : { "_index" : "login", "_type" : "log", "_id" : "1" } }
{ "user_id" : "u001", "user_name" : "Maker", "login_date" : "2018-01-01 00:00:00" }
{ "index" : { "_index" : "login", "_type" : "log", "_id" : "2" } }
{ "user_id" : "u002", "user_name" : "Park", "login_date" : "2018-01-01 08:00:00" }
{ "index" : { "_index" : "login", "_type" : "log", "_id" : "3" } }
{ "user_id" : "u003", "user_name" : "Link", "login_date" : "2018-01-02 07:00:00" }

2.然后使用post请求发送给elasticsearch

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/login/log/_bulk?pretty" --data-binary @login.json

3.http返回状态是200则表示数据插入成功

数据删除

elasticsearch删除数据的方式有两种,一种是直接删除索引,另一种是根据查询的结果删除数据,需要注意的是,如果是只删除数据,elasticsearch是软删除,不是物理直接删除,设计到的vserion版本控制问题,以后会有说明。

1.索引删除方式。

curl -XDEL localhost:9200/my_index

2.根据搜索结果去删除数据。

curl -XPOST 'localhost:9200/my_index/_delete_by_query?pretty' -H 'Content-Type: application/json' -d'
{
  "query": { 
    "match": {
      "message": "some message"
    }
  }
}
'

数据修改

elasticsearch的数据修改只需要查询出需要修改数据的_id,然后覆盖即可进行修改,涉及到version版本控制的以后修改。

数据查询

elasticsearch最强大的是其搜索功能,下面将介绍其主要搜索功能,后面也会随时补充。在介绍前,需要详细得了解一下elasticsearch的评分功能。

评分

每个文档都有相关性评分,用一个正浮点数字段 _score 来表示 。 _score 的评分越高,相关性越高。

查询语句会为每个文档生成一个 _score 字段。评分的计算方式取决于查询类型 不同的查询语句用于不同的目的: fuzzy 查询会计算与关键词的拼写相似程度,terms 查询会计算 找到的内容与关键词组成部分匹配的百分比,但是通常我们说的 relevance 是我们用来计算全文本字段的值相对于全文本检索词相似程度的算法。

Elasticsearch 的相似度算法 被定义为检索词频率/反向文档频率, TF/IDF ,包括以下内容:

  • 检索词频率

检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。

  • 反向文档频率

每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。

  • 字段长度准则

字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大。
单个查询可以联合使用 TF/IDF 和其他方式,比如短语查询中检索词的距离或模糊查询里的检索词相似度。

相关性并不只是全文本检索的专利。也适用于 yes|no 的子句,匹配的子句越多,相关性评分越高。

详情参考官方文档https://www.elastic.co/guide/cn/elasticsearch/guide/current/relevance-intro.html

一般查询

在接下来的例子中都会以之前的登录为例子。

1.简单查询

  • match,该查询会影响评分,以致于影响查询结果,相关度越高,搜索结果越靠前。但是如果是查询中文,则会将中文进行分词,所以该查询不适合精确查询。
GET login/log/_search
{
  "query": {
    "match": {
      "user_name": "Park"
    }
  }
}

#返回结果

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "login",
        "_type": "log",
        "_id": "2",
        "_score": 0.2876821,
        "_source": {
          "user_id": "u002",
          "user_name": "Park",
          "login_date": "2018-01-01 08:00:00"
        }
      }
    ]
  }
}
  • match_all 查询所有信息
  • term查询,可以用来精确查询,不会讲词语进行分词查询,如果查询多个值可以用terms,里面接数组。
GET test1/log/_search
{
  "query": {
    "term": {
      "user_name": {
        "value": "Park"
      }
    }
  }
}

#返回结果
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "test1",
        "_type": "log",
        "_id": "2",
        "_score": 0.2876821,
        "_source": {
          "user_id": "u002",
          "user_name": "Park",
          "login_date": "2018-01-01 08:00:00"
        }
      }
    ]
  }
}
  • range查询,查找出指定区间的数字或者时间
#查询1月1日有多少次登录
curl -XPOST 'localhost:9200/test1/log/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "range": {
      "login_date": {
        "gte": "2018-01-01 00:00:00",
        "lt": "2018-01-02 00:00:00"
      }
    }
  }
}'

#返回结果{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "test1",
        "_type" : "log",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "user_id" : "u002",
          "user_name" : "Park",
          "login_date" : "2018-01-01 08:00:00"
        }
      },
      {
        "_index" : "test1",
        "_type" : "log",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "user_id" : "u001",
          "user_name" : "Maker",
          "login_date" : "2018-01-01 00:00:00"
        }
      }
    ]
  }
}
  • regexp 正则查询
#查询118开头的id
POST test/_search
{
  "query": {
    "regexp":{
      "id":{
        "value":"118.*",
        "boost":1.2
      }
    }
  }
}
组合查询

为了将条件组合起来查询比如数据库中的AND OR等类似条件,可以使用组合查询来实现。首先需要了解以下概念。为了构建类似的高级查询,你需要一种能够将多查询组合成单一查询的查询方法。你可以用 bool 查询来实现你的需求。这种查询将多查询组合在一起,成为用户自己想要的布尔查询。它接收以下参数:

must

文档 必须 匹配这些条件才能被包含进来。

must_not

文档 必须不 匹配这些条件才能被包含进来。

should

如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分。

filter

必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。

  • bool查询
#例如假设一个登录索引中有如下字段,"user_id":"用户id", "user_os":"用户系统", "game_version":"游戏版本", "role_name":"角色名称",先需要查询用户系统必须是IOS,并且游戏版本不能低于0.2,角色名称可以不叫reboot的用户,查询语句如下

curl -XPOST 'localhost:9200/test1/log/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": {
        "term": {
          "user_os": "IOS"
        }
      },
      "must_not": {
        "range": {
          "game_id": {
            "lte": "0.2"
          }
        }
      },
      "should": {
        "term": {
          "user_id": "reboot"
        }
      }
    }
  }
}
'
  • constant_score查询

包装另一个其他类型的查询,并简单地返回一个常数分数,该分数等于筛选器中每个文档的查询提升。

#查询18年1月1日以后登录过的人数,并返回的评分都是1
curl -XPOST 'localhost:9200/test1/log/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "login_date": {
            "gte": "2018-01-01 00:00:00"
          }
        }
      }
    }
  }
}
'

关联查询(父子关系)

关联查询就是将elasticsearch建立关联关系进行查询,elasticsearch是非关系型数据库,经过大量实验,如果需要进行类似sql的多表查询就需要将elasticsearch的mapping进行建立父子关系,以下例子将具体说明,特别说明在elasticsearch6.0版本后官方取消了type这个字段,为了方便使用我这里用的版本是elasticsearch5.6.7的版本。为了方便演示,我创建了两张表,用户表和登录表进行示例查询。

创建mapping
{
  "mappings": {
    "user": {
      "properties": {
           "user_id": { "type": "keyword" },
           "user_name": { "type": "keyword" },
           "reg_date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
          }
    },
    "login": {
      "_parent": {
        "type": "user" 
      },
      "properties": {
            "user_id": { "type": "keyword" },
            "user_name": { "type": "keyword" },
            "login_date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
      }
    }
  }
}
#_parent的意思是指定login的父为user
创建父表
#父表是用户表,里面包含用户的基本信息以及注册时间等。其中字段re_date是注册时间。
cat user.json
{ "index": { "_id": "u001" }}
{ "user_id": "u001", "user_name": "test1", "re_date": "2018-01-01 00:00:00" }
{ "index": { "_id": "u002" }}
{ "user_id": "u002", "user_name": "test2", "re_date": "2018-01-01 03:03:23" }
{ "index": { "_id": "u003" }}
{ "user_id": "u003", "user_name": "test3", "re_date": "2018-01-01 10:43:28" }
{ "index": { "_id": "u004" }}
{ "user_id": "u004", "user_name": "test4", "re_date": "2018-01-02 18:03:55" }
{ "index": { "_id": "u005" }}
{ "user_id": "u005", "user_name": "test5", "re_date": "2018-01-02 10:33:53" }
#需要注意的是在我的这个场景中user的id是唯一的,然后我将user_id中的值对应了索引中的_id,如果是其他场景需要注意这个问题
curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/test/user/_bulk?pretty" --data-binary @user.json
创建子表
#子表示登录表,里面包含用户的登录信息以及登录时间,其中字段login_date是登录时间。
cat login.json
{ "index": { "_id": "1", "parent": "u001" }}
{ "user_id": "u001", "user_name": "test1", "login_date": "2018-01-01 00:00:01" }
{ "index": { "_id": "2", "parent": "u001" }}
{ "user_id": "u001", "user_name": "test1", "login_date": "2018-01-02 00:00:00" }
{ "index": { "_id": "3", "parent": "u001" }}
{ "user_id": "u001", "user_name": "test1", "login_date": "2018-01-01 00:10:20" }
{ "index": { "_id": "4", "parent": "u003" }}
{ "user_id": "u003", "user_name": "test3", "login_date": "2018-01-01 04:50:00" }
{ "index": { "_id": "5", "parent": "u003" }}
{ "user_id": "u003", "user_name": "test3", "login_date": "2018-01-03 00:00:00" }
{ "index": { "_id": "6", "parent": "u001" }}
{ "user_id": "u001", "user_name": "test1", "login_date": "2018-01-03 00:10:00" }
{ "index": { "_id": "7", "parent": "u004" }}
{ "user_id": "u004", "user_name": "test4", "login_date": "2018-01-02 20:00:00" }
{ "index": { "_id": "8", "parent": "u005" }}
{ "user_id": "u005", "user_name": "test5", "login_date": "2018-01-02 11:00:00" }
{ "index": { "_id": "9", "parent": "u003" }}
{ "user_id": "u003", "user_name": "test3", "login_date": "2018-01-04 02:00:00" }
{ "index": { "_id": "10", "parent": "u004" }}
{ "user_id": "u004", "user_name": "test4", "login_date": "2018-01-05 00:00:00" }
#parent是那user_id与上面的父亲表做了关联,这样关系就已经建立好了。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/test/login/_bulk?pretty" --data-binary @login.json
子查询

通过子文档查询父文档,例如查询某个时间段内的注册登陆。

POST test/user/_search
{
  "query": {
    "has_child": {
      "type": "login",
      "query": {
        "constant_score": {
          "filter": {
            "range": {
              "login_date": {
                "gte": "2018-01-01 00:00:00",
                "lt": "2018-01-02 00:00:00"
              }
            }
          }
        }
      }
    }
  }
}

#返回结果
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "test",
        "_type": "user",
        "_id": "u003",
        "_score": 1,
        "_source": {
          "user_id": "u003",
          "user_name": "test3",
          "re_date": "2018-01-01 10:43:28"
        }
      },
      {
        "_index": "test",
        "_type": "user",
        "_id": "u001",
        "_score": 1,
        "_source": {
          "user_id": "u001",
          "user_name": "test1",
          "re_date": "2018-01-01 00:00:00"
        }
      }
    ]
  }
}
父查询

通过父文档查询子文档,只要修改上面的例子,将has_child改成has_parent然后在加入要查询的条件即可,这里不再重复

聚合

elasticsearch的聚合功能也很强大,便于数据的分析,类似于数据库中group by功能。

指标聚合
  • max聚合,求一个数的最大统计值

例如统计最大充值金额

{
  "aggs": {
    "max_recharge": { ①
      "max": {
        "field": "recharge_amount"
      }
    }
  }
}
①此部分可以自己定义函数名称
  • min聚合,求一个数的最小统计值

例如统计最小充值金额

{
  "aggs": {
    "min_recharge": { ①
      "min": {
        "field": "recharge_amount"
      }
    }
  }
}
①此部分可以自己定义函数名称
  • avg聚合,求一个数的平均统计值

例如统计平均充值金额

{
  "aggs": {
    "avg_recharge": { ①
      "avg": {
        "field": "recharge_amount"
      }
    }
  }
}
①此部分可以自己定义函数名称
  • sun聚合,计算总和

例如统计总充值金额

{
  "aggs": {
    "sum_recharge": { ①
      "sum": {
        "field": "recharge_amount"
      }
    }
  }
}
①此部分可以自己定义函数名称
  • stats聚合,用于基本统计,会一次返回count,max,mix,avg,sum这个五个值。
  • extemded_ststs聚合,用于高级统计,会比stats多返回四个值,分别是平方和,方差,标准和,平均值加/减两个标准差的区间。

  • cardinality聚合,用于数据去重。

例如统计一共有多少人充值,要求去重

{
  "aggs": {
    "uv_recharge": { ①
      "cardinality": {
        "field": "user_id"
      }
    }
  }
}
①此部分可以自己定义函数名称

桶聚合

我的理解就是分组聚合,将不同数据的相同字段进行聚合。

  • terms 聚合

例如统计每个用户登录过几次

POST test/login/_search
{
  "size": 0, 
  "aggs": {
    "user_login": {
      "terms": {
        "field": "user_id"
      }
    }
  }
}

#返回结果
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "user_login": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "u001",
          "doc_count": 4
        },
        {
          "key": "u003",
          "doc_count": 3
        },
        {
          "key": "u004",
          "doc_count": 2
        },
        {
          "key": "u005",
          "doc_count": 1
        }
      ]
    }
  }
}
  • filter聚合,可以把符合条件的文档聚合在一起。

例如求user_id为u001的登录次数

POST test/login/_search
{
  "size": 0, 
  "aggs": {
    "u001_login": {
      "filter": {
        "term": {
          "user_id": "u001"
        }
      },
      "aggs": {
        "login": {
          "cardinality": {
            "field": "user_id"
          }
        }
      }
    }
  }
}

#返回结果
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "u001_login": {
      "doc_count": 4,
      "login": {
        "value": 1
      }
    }
  }
}
  • filters聚合,多过滤聚合,例如求u001,u002的登录次数
POST test/login/_search
{
  "size": 0, 
  "aggs": {
    "user_log": {
      "filters": {
        "filters": [
          {"term": { "user_id": "u001"} },
          {"term": { "user_id": "u002"} }
       ]
      },
      "aggs": {
        "login": {
          "cardinality": {
            "field": "user_id"
          }
        }
      }
    }
  }
}

#返回结果
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "user_log": {
      "buckets": [
        {
          "doc_count": 4,
          "login": {
            "value": 1
          }
        },
        {
          "doc_count": 0,
          "login": {
            "value": 0
          }
        }
      ]
    }
  }
}
  • range聚合,反应数据的时间段分布情况

例如求第一天和第二天的登录次数

POST test/login/_search
{
  "size": 0, 
  "aggs": {
    "range_time": {
      "range": {
        "field": "login_date",
        "ranges": [
          {
            "from": "2018-01-01 00:00:00",
            "to": "2018-01-01 23:59:59"
          },
          {
            "from": "2018-01-02 00:00:00",
            "to": "2018-01-02 23:59:59"
          }
        ]
      }
    }
  }
}

#返回结果
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "range_time": {
      "buckets": [
        {
          "key": "2018-01-01 00:00:00-2018-01-01 23:59:59",
          "from": 1514764800000,
          "from_as_string": "2018-01-01 00:00:00",
          "to": 1514851199000,
          "to_as_string": "2018-01-01 23:59:59",
          "doc_count": 3
        },
        {
          "key": "2018-01-02 00:00:00-2018-01-02 23:59:59",
          "from": 1514851200000,
          "from_as_string": "2018-01-02 00:00:00",
          "to": 1514937599000,
          "to_as_string": "2018-01-02 23:59:59",
          "doc_count": 3
        }
      ]
    }
  }
}
  • date_range聚合,专门的日期聚合,可以指定日期格式
POST test/login/_search
{
  "size": 0, 
  "aggs": {
    "date_range": {
      "date_range": {
        "field": "login_date",
        "format": "YYYY-MM-dd", 
        "ranges": [
          {
            "from": "2018-01-01",
            "to": "2018-01-02"
          },
          {
            "from": "2018-01-02",
            "to": "2018-01-04"
          }
        ]
      }
    }
  }
}
#返回结果
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "date_range": {
      "buckets": [
        {
          "key": "2018-01-01-2018-01-02",
          "from": 1514764800000,
          "from_as_string": "2018-01-01",
          "to": 1514851200000,
          "to_as_string": "2018-01-02",
          "doc_count": 3
        },
        {
          "key": "2018-01-02-2018-01-04",
          "from": 1514851200000,
          "from_as_string": "2018-01-02",
          "to": 1515024000000,
          "to_as_string": "2018-01-04",
          "doc_count": 5
        }
      ]
    }
  }
}

借助脚本聚合

elasticsearch求交集

有一些复杂统计,需要时间段内去交集,这样就要用到脚本进行统计

例如求时间段内的用户DNU,sql如下

select user_id from login where login_date >="2018-01-01 00:00:00" and login_date < "2018-01-01 23:59:59" and user_id in(select  user_id from login  where  date >="2018-01-02 00:00:00" and  login_date < "2018-01-02 23:59:59")

对照elasticsearch查询语句如下

#这里需要注意的是,脚本没有办法去计算data类型的,为了方便计算需要将data类型转换成unix时间戳类型,数据类型为long。
{
  "size": 0,
  "query": {
    "bool": {
      "should": [
        {
          "range": {
            "es_login_date": {
              "gte": "1514736000000",
              "lte": "1514736420000"
            }
          }
        },
        {
          "range": {
            "es_login_date": {
              "gte": "1514736480000",
              "lte": "1514736540000"
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "intersect": {
      "scripted_metric": {
        "init_script": "params._agg.a=new HashSet();params._agg.b=new HashSet()",
        "map_script": "if(doc['es_login_date'].value >= 1514736000000L&&doc['es_login_date'].value <= 1514736420000L){params._agg['a'].add(doc['user_id'].value);}if(doc['es_login_date'].value >= 1514736480000L&&doc['es_login_date'].value <= 1514736540000L){params._agg['b'].add(doc['user_id'].value)}",
        "reduce_script": "def a=new HashSet(),b=new HashSet();params._aggs.forEach(item->{a.addAll(item.a);b.addAll(item.b);});a.retainAll(b);return a"
      }
    }
  }
}

elasticsearch时间格式化问题

假设我们有一个login表,mapping的login_date登录时间字段为"login_date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" },需要是搜索一个月有多少人登录的时候,前端只给传月份,如果后端不想做修改直接使用,这是就需要将时间格式。

{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "login_date": {
            "gte": "2017-07",
            "lte": "2017-08",
            "format": "yyyy-MM"
          }
        }
      }
    }
  }
}


#搜索时使用format将时间进行格式化

接下来我们想搜索2017-07-28号这一天的登录情况,但是有个需求是需要返回0-23点聚合后的数据,这是需要让聚合结果进行格式化

{
  "size": 0, 
  "query": {
    "bool": {
      "filter": {
        "range": {
          "login_date": {
            "gte": "2017-07-28 00:00:00",
            "lte": "2017-07-28 23:59:59"
          }
        }
      }
    }
  },
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "login_date",
        "interval": "hour",
        "format": "HH"
      }
    }
  }
}

#返回结果
"aggregations": {
    "test": {
      "buckets": [
        {
          "key_as_string": "00",
          "key": 1501200000000,
          "doc_count": 28085
        },
        {
          "key_as_string": "01",
          "key": 1501203600000,
          "doc_count": 9369
        },
        {
          "key_as_string": "02",
          "key": 1501207200000,
          "doc_count": 5700
        },
        {
          "key_as_string": "03",
          "key": 1501210800000,
          "doc_count": 3578
        },
        {
          "key_as_string": "04",
          "key": 1501214400000,
          "doc_count": 2791
        },
        {
          "key_as_string": "05",
          "key": 1501218000000,
          "doc_count": 4132
        },
        {
          "key_as_string": "06",
          "key": 1501221600000,
          "doc_count": 8987
        },
        {
          "key_as_string": "07",
          "key": 1501225200000,
          "doc_count": 9117
        },
        {
          "key_as_string": "08",
          "key": 1501228800000,
          "doc_count": 14073
        },
        {
          "key_as_string": "09",
          "key": 1501232400000,
          "doc_count": 22577
        },
        {
          "key_as_string": "10",
          "key": 1501236000000,
          "doc_count": 33646
        },
        {
          "key_as_string": "11",
          "key": 1501239600000,
          "doc_count": 23336
        },
        {
          "key_as_string": "12",
          "key": 1501243200000,
          "doc_count": 23
        },
        {
          "key_as_string": "13",
          "key": 1501246800000,
          "doc_count": 23
        },
        {
          "key_as_string": "14",
          "key": 1501250400000,
          "doc_count": 16
        },
        {
          "key_as_string": "15",
          "key": 1501254000000,
          "doc_count": 5
        },
        {
          "key_as_string": "16",
          "key": 1501257600000,
          "doc_count": 20
        },
        {
          "key_as_string": "17",
          "key": 1501261200000,
          "doc_count": 12
        },
        {
          "key_as_string": "18",
          "key": 1501264800000,
          "doc_count": 20
        },
        {
          "key_as_string": "19",
          "key": 1501268400000,
          "doc_count": 21
        },
        {
          "key_as_string": "20",
          "key": 1501272000000,
          "doc_count": 12
        },
        {
          "key_as_string": "21",
          "key": 1501275600000,
          "doc_count": 28
        },
        {
          "key_as_string": "22",
          "key": 1501279200000,
          "doc_count": 20
        },
        {
          "key_as_string": "23",
          "key": 1501282800000,
          "doc_count": 172
        }
      ]
    }

elasticsearch 聚合结果值操作

我们有这样一个需求,我相求某以时间内的平均在线人数,时间字段为time,在线人数字段为on_line,我们的一般做法为

{
  "size": 0, 
  "query": {
    "bool": {
      "filter": {
          "range": {
            "heart_time": {
              "gte": "2017-12-23 00:00:00",
              "lt": "2017-12-23 00:10:00"
            }
          }
        }
    }
  },
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "heart_time",
        "interval": "minute"
      }, 
      "aggs": {
        "test2": {
          "avg": {
            "field": "on_line"
          }
        }
      }
    }
  }
}

#返回结果是
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "test": {
      "buckets": [
        {
          "key_as_string": "2017-12-23 00:00:00",
          "key": 1513987200000,
          "doc_count": 2,
          "test2": {
            "value": 846.5
          }
        },
        {
          "key_as_string": "2017-12-23 00:01:00",
          "key": 1513987260000,
          "doc_count": 0,
          "test2": {
            "value": null
          }
        },
        {
          "key_as_string": "2017-12-23 00:02:00",
          "key": 1513987320000,
          "doc_count": 0,
          "test2": {
            "value": null
          }
        },
        {
          "key_as_string": "2017-12-23 00:03:00",
          "key": 1513987380000,
          "doc_count": 0,
          "test2": {
            "value": null
          }
        },
        {
          "key_as_string": "2017-12-23 00:04:00",
          "key": 1513987440000,
          "doc_count": 0,
          "test2": {
            "value": null
          }
        },
        {
          "key_as_string": "2017-12-23 00:05:00",
          "key": 1513987500000,
          "doc_count": 2,
          "test2": {
            "value": 798
          }
        }
      ]
    }
  }
}

这样做看似很好,但是有一个问题,当值不存在时会返回一个null的空值,为了不让这种结果出现,解决方式是使用bucket_selector,对聚合结果进行操作

{
  "size": 0, 
  "query": {
    "bool": {
      "filter": {
          "range": {
            "heart_time": {
              "gte": "2017-12-23 00:00:00",
              "lt": "2017-12-23 00:30:00"
            }
          }
        }
    }
  },
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "heart_time",
        "interval": "minute"
      }, 
      "aggs": {
        "test2": {
          "avg": {
            "field": "on_line"
          }
        },
        "test3": {
          "bucket_selector": {
            "buckets_path": { 
            "count": "_count" ①
            },
            "script": {
              "lang": "expression", ②
              "source": "count > 0" ③
            }
          }
        }
      }
    }
  }
}
#①代表聚合的路径,_count表示最外层的聚合,也就是说对test的聚合结果进行操作。
 ②表示使用脚本,语言为expression,key值可以自定义,传递给③用。
 ③聚合结果进行操作,大于0表示,排除掉null,但是不能等于0,如果等于0的话显示为空。

#返回结果
"aggregations": {
    "test": {
      "buckets": [
        {
          "key_as_string": "2017-12-23 00:00:00",
          "key": 1513987200000,
          "doc_count": 2,
          "test2": {
            "value": 846.5
          }
        },
        {
          "key_as_string": "2017-12-23 00:05:00",
          "key": 1513987500000,
          "doc_count": 2,
          "test2": {
            "value": 798
          }
        },
        {
          "key_as_string": "2017-12-23 00:10:00",
          "key": 1513987800000,
          "doc_count": 2,
          "test2": {
            "value": 761.5
          }
        },
        {
          "key_as_string": "2017-12-23 00:15:00",
          "key": 1513988100000,
          "doc_count": 2,
          "test2": {
            "value": 723
          }
        },
        {
          "key_as_string": "2017-12-23 00:20:00",
          "key": 1513988400000,
          "doc_count": 2,
          "test2": {
            "value": 708
          }
        },
        {
          "key_as_string": "2017-12-23 00:25:00",
          "key": 1513988700000,
          "doc_count": 2,
          "test2": {
            "value": 670
          }
        }
      ]
    }
  }

猜你喜欢

转载自blog.csdn.net/z619193774/article/details/79445739