从零学Elasticsearch系列——深入搜索(Query、Filter、Aggregation)

系列文章:

  • 从零学Elasticsearch系列——基础概念
  • 从零学Elasticsearch系列——环境搭建
  • 从零学Elasticsearch系列——使用kibana实现ES基本的操作
  • 从零学Elasticsearch系列——深入搜索(Query、Filter、Aggregation)
  • 从零学Elasticsearch系列——JAVA API操作
  • 从零学Elasticsearch系列——集成中文分词器IK
  • 从零学Elasticsearch系列——构建ES集群
  • 从零学Elasticsearch系列——搭建ELK Nginx日志分析平台

搜索方式

搜索有两种方式:一种是通过URL参数进行搜索,另一种是通过DSL(Request Body)进行搜索

DSL:Domain Specified Language,特定领域语言

使用请求体可以让你的JSON数据以一种更加可读和更加富有展现力的方式发送。

导入测试数据集

# 批量插入测试数据
POST /zpark/user/_bulk
{"index":{"_id":1}}
{"name":"zs","realname":"张三","age":18,"birthday":"2018-12-27","salary":1000.0,"address":"北京市昌平区沙阳路55号"}
{"index":{"_id":2}}
{"name":"ls","realname":"李四","age":20,"birthday":"2017-10-20","salary":5000.0,"address":"北京市朝阳区三里屯街道21号"}
{"index":{"_id":3}}
{"name":"ww","realname":"王五","age":25,"birthday":"2016-03-15","salary":4300.0,"address":"北京市海淀区中关村大街新中关商城2楼511室"}
{"index":{"_id":4}}
{"name":"zl","realname":"赵六","age":20,"birthday":"2003-04-19","salary":12300.0,"address":"北京市海淀区中关村软件园9号楼211室"}
{"index":{"_id":5}}
{"name":"tq","realname":"田七","age":35,"birthday":"2001-08-11","salary":1403.0,"address":"北京市海淀区西二旗地铁辉煌国际大厦负一楼"}

查询(Query)

1. 查看所有并按照年龄降序排列

查询所有并排序

  • URL实现

    GET /zpark/user/_search?q=*&sort=age:desc&pretty
    
  • DSL实现

    GET /zpark/user/_search
    {
      "query":{
        "match_all":{}	# 查询所有
      },
      "sort":{
          "age":"desc"  # 按年龄倒序排列
        }
    }
    

2. 查询第2页的用户(每页显示2条)

分页查询

  • URL实现

    GET /zpark/user/_search?q=*&sort=_id:asc&from=2&size=2
    
  • DSL实现

    GET /zpark/user/_search
    {
      "query":{
        "match_all":{}	# 查询所有
      },
      "sort":{
          "_id":"asc"   # 按年龄倒序排列
       },
      "from":2,			# 从(nowPage-1*pageSize检索
      "size":2   		# 查 pageSize条
    }
    

3. 查询address在海淀区的所有用户,并高亮

基于全文检索的查询(分析检索关键词 匹配索引库 返回结果)

  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "match": {  # 注意: match查询会分词 如:海淀区 会分词为 海 ||"address":"海淀区"
        }
      },
      "highlight": {
        "fields": {		# 需要高亮的字段列表
          "address": {}   
        }
      }
    }
    

4. 查询namezs关键字的用户

基于Term词元查询

  • URL实现

    GET /zpark/user/_search?q=name:zs
    
  • DSL实现

    GET /zpark/user/_search
    {
      "query":{
        "term": {
          "name": {
            "value": "zs"
          }
        }
      }
    }
    

5. 查询年龄在20~30岁之间的用户

基于范围查询

  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "range": {
          "age": {
            "gte": 20,   # 大于等于  大于用 gt
            "lte": 30    # 小于等于  小于用 lt
          }
        }
      }
    }
    

6. 查询真实姓名以开头的用户

基于前缀(prefix)查询

  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "prefix": {
          "realname": {
            "value": "李"
          }
        }
      }
    }
    

7. 查询名字已s结尾的用户

基于通配符(wildcard)的查询

  • 匹配一个字符
  • * 匹配0~n个字符
  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "wildcard": {
          "name": {
            "value": "*s"
          }
        }
      }
    }
    

8. 查询id为1,2,3的用户

基于Ids的查询

  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "ids": {
          "values": [1,2,3]
        }
      }
    }
    

9. 模糊查询realname中包含关键字的用户

基于Fuzzy的查询

  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "fuzzy": {
          "realname": {"value": "张"}
        }
      }
    }
    

10. 查询age在15-30岁之间并且name必须通配z*

基于Boolean的查询(多条件查询)

  • must:查询结果必须符合该查询条件(列表)。
  • should:类似于or的查询条件。
  • must_not:查询结果必须不符合查询条件(列表)。
  • DSL实现

    GET /zpark/user/_search
    {
      "query": {
        "bool": {
          "must": [	   #年龄在15~30岁之间并且必须名字通配z*
            {
              "range": {
                "age": {
                  "gte": 15,
                  "lte": 30
                }
              }
            },
            {
              "wildcard": {
                "name": {
                  "value": "z*"
                }
              }
            }
          ]"must_not": [  # 正则查询 name必须不能以s结尾
            {
              "regexp": {
                "name": ".*s"
              }
            }
          ] 
        }
      }
    }
    

过滤器(Filter)

其实准确来说,ES中的查询操作分为2种:查询(query)和过滤(filter)。查询即是之前提到的query查询,它(查询)默认会计算每个返回文档的得分,然后根据得分排序。而过滤(filter)只会筛选出符合的文档,并不计算得分,且它可以缓存文档。所以,单从性能考虑,过滤比查询更快。

换句话说,过滤适合在大范围筛选数据,而查询则适合精确匹配数据。一般应用时,应先使用过滤操作过滤数据,然后使用查询匹配数据。

过滤器使用

GET /zpark/user/_search
{
   "query":{
      "bool": {
        "must": [
          {"match_all": {}}
        ],
        "filter": {		# 过滤年龄大于等于25岁的用户
          "range": {
            "age": {
              "gte": 25
            }
          }
        }
      }
   }
}

注意: 过滤查询运行时先执行过滤语句,后执行普通查询

过滤器的类型

1. termterms Filter

term、terms的含义与查询时一致。term用于精确匹配、terms用于多词条匹配

GET /zpark/user/_search
{
   "query":{
      "bool": {
        "must": [
          {"match_all": {}}
        ],
        "filter": {
          "terms": {
            "name": [
              "zs",
              "ls"
            ]
          }
        }
      }
   }
}
2. ranage filter
3. exists filter

exists过滤指定字段没有值的文档

GET /zpark/user/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {    # 排除salary为null的结果
        "exists": {
          "field": "salary"
        }
      }
    }
  },
  "sort": [
    {
      "_id": {
        "order": "asc"
      }
    }
  ]
}

或(相反操作)

GET /zpark/user/_search
{ 
  "query": {
    "bool": {
      "must_not": [
        {
          "exists": {
            "field": "salary"
          }
        }
      ]
    }
  }
}
4. ids filter

需要过滤出若干指定_id的文档,可使用标识符过滤器(ids)

GET /zpark/user/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "昌平区"
          }
        }
      ],
      "filter": {
        "ids": {   # id 过滤器
          "values": [
            1,
            2,
            3
          ]
        }
      }
    }
  }
}

5. 其余使用方式可查阅官网

Note:

Query和Filter更详细的对比可参考:https://blog.csdn.net/laoyang360/article/details/80468757

聚合(Aggregations)

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/search-aggregations.html

聚合提供了功能可以分组并统计你的数据。理解聚合最简单的方式就是可以把它粗略的看做SQL的GROUP BY操作和SQL的聚合函数。

ES中常用的聚合:

  • metric(度量)聚合:度量类型聚合主要针对的number类型的数据,需要ES做比较多的计算工作,类似于关系型数据库的组函数操作
  • bucketing(桶)聚合:划分不同的“桶”,将数据分配到不同的“桶”里。非常类似sql中的group语句的含义,类似于关系型数据库的分组操作

ES中的聚合API如下:

"aggregations" : {                  // 表示聚合操作,可以使用aggs替代
    "" : {        // 聚合名,可以是任意的字符串。用做响应的key,便于快速取得正确的响应数据。
        "" : {    // 聚合类别,就是各种类型的聚合,如min等
            <aggregation_body>      // 聚合体,不同的聚合有不同的body
        }
        [,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套的子聚合,可以有0或多个
    }
    [,"" : { ... } ]* // 另外的聚合,可以有0或多个
}

度量(metric)聚合

1. Avg Aggregation

平均值查询,作用于number类型字段上。如:查询用户的平均年龄

POST /zpark/user/_search
{
  "aggs": {
    "age_avg": {
      "avg": {"field": "age"}
    }
  }
}
-------------------------------------------------------------------
{
    ......
    "aggregations": {
    "age_avg": {
      "value": 23.6
    }
  }
}

也可以先过滤,再进行统计,如:

POST /zpark/user/_search
{ "query": {
    "ids": {
      "values":[1,2,3]
    }
  }, 
  "aggs": {
    "age_avg": {
      "avg": {"field": "age"}
    }
  }
}
2. Max Aggregation

最大值查询。如:查询员工的最高工资

POST /zpark/user/_search
{
  "aggs": {
    "max_salary": {
      "max": {
        "field": "salary"
      }
    }
  }
}
3. Min Aggregation
4. Sum Aggregation
5. [Stats Aggregation](Stats Aggregation)

统计查询,一次性统计出某个字段上的常用统计值

POST /zpark/user/_search
{
  "aggs": {
    "max_salary": {
      "stats": {
        "field": "salary"
      }
    }
  }
}
--------------------------------------------
{
    ....
    "aggregations": {
    "max_salary": {
      "count": 4,
      "min": 1000,
      "max": 12300,
      "avg": 5650,
      "sum": 22600
    }
  }
}

桶(bucketing)聚合

1. Range Aggregation

自定义区间范围的聚合,我们可以自己手动地划分区间,ES会根据划分出来的区间将数据分配不同的区间上去。

如: 统计0-20岁,20-35岁,35~60岁用户人数

POST /zpark/user/_search
{
  "aggs": {
    "age_ranges": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 0,
            "to": 20
          },
          {
            "from": 20,
            "to": 35
          },
          {
            "from": 35,
            "to": 60
          }
        ]
      }
    }
  }
}
----------------------------------------------------------
{
    ......
    "aggregations": {
    "age_ranges": {
      "buckets": [
        {
          "key": "0.0-20.0",
          "from": 0,
          "to": 20,
          "doc_count": 1   # 区间范围的文档数量
        },
        {
          "key": "20.0-35.0",
          "from": 20,
          "to": 35,
          "doc_count": 3
        },
        {
          "key": "35.0-60.0",
          "from": 35,
          "to": 60,
          "doc_count": 1
        }
      ]
    }
  }
}
2. Terms Aggregation

自定义分组依据Term,对分组后的数据进行统计

如:根据年龄分组,统计相同年龄的用户

POST /zpark/user/_search
{
  "aggs": {
    "age_counts":{
      "terms": {
        "field": "age",
        "size": 2  // 保留2个统计结果
      }
    }
  }
}
----------------------------------------------------------
{
    ......
    "aggregations": {
    "age_counts": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 2,
      "buckets": [
        {
          "key": 20,
          "doc_count": 2
        },
        {
          "key": 18,
          "doc_count": 1
        }
      ]
    }
  }
}
3. Date Range Aggregation

时间区间聚合专门针对date类型的字段,它与Range Aggregation的主要区别是其可以使用时间运算表达式。

  • now+10y:表示从现在开始的第10年。
  • now+10M:表示从现在开始的第10个月。
  • 1990-01-10||+20y:表示从1990-01-01开始后的第20年,即2010-01-01。
  • now/y:表示在年位上做舍入运算。

如: 统计生日在2018年、2017年、2016年的用户

POST /zpark/user/_search
{
  "aggs": {
    "date_counts": {
      "date_range": {
        "field": "birthday",
        "format": "yyyy-MM-dd", 
        "ranges": [
          {
            "from": "now/y",  # 当前年的11"to": "now"       # 当前时间
          },
          {
            "from": "now/y-1y",  # 当前年上一年的11"to":"now/y"         # 当前年的11},
          {
            "from": "now/y-2y",  
            "to":"now/y-1y"
          }
        ]
      }
    }
  }
}
-------------------------------------------------------------------------------
{
    .....
  "aggregations": {
    "date_counts": {
      "buckets": [
        {
          "key": "2016-01-01-2017-01-01",
          "from": 1451606400000,
          "from_as_string": "2016-01-01",
          "to": 1483228800000,
          "to_as_string": "2017-01-01",
          "doc_count": 1
        },
        {
          "key": "2017-01-01-2018-01-01",
          "from": 1483228800000,
          "from_as_string": "2017-01-01",
          "to": 1514764800000,
          "to_as_string": "2018-01-01",
          "doc_count": 1
        },
        {
          "key": "2018-01-01-2018-12-26",
          "from": 1514764800000,
          "from_as_string": "2018-01-01",
          "to": 1545847233691,
          "to_as_string": "2018-12-26",
          "doc_count": 0
        }
      ]
    }
  }
}
4. Histogram Aggregation

直方图聚合,它将某个number类型字段等分成n份,统计落在每一个区间内的记录数。它与前面介绍的Range聚合非常像,只不过Range可以任意划分区间,而Histogram做等间距划分。既然是等间距划分,那么参数里面必然有距离参数,就是interval参数。

如:根据年龄间隔(5岁)统计

POST /zpark/user/_search
{
  "aggs": {
    "histogram_age": {
      "histogram": {
        "field": "age",
        "interval": 5
      }
    }
  }
}
-------------------------------------------------------------------------------
{
    ......
    "aggregations": {
    "histogram_age": {
      "buckets": [
        {
          "key": 15,
          "doc_count": 1
        },
        {
          "key": 20,
          "doc_count": 2
        },
        {
          "key": 25,
          "doc_count": 1
        },
        {
          "key": 30,
          "doc_count": 0
        },
        {
          "key": 35,
          "doc_count": 1
        }
      ]
    }
  }
}
5. Date Histogram Aggregation

日期直方图聚合,专门对时间类型的字段做直方图聚合。这种需求是比较常用见得的,我们在统计时,通常就会按照固定的时间断(1个月或1年等)来做统计。

如:按年统计用户

POST /zpark/user/_search
{
  "aggs": {
    "date_histogram": {
      "date_histogram": {
        "field": "birthday",
        "interval": "year",
        "format": "yyyy-MM-dd"
      }
    }
  }
}
-------------------------------------------------------------------------------
{
    ......
    "aggregations": {
    "date_histogram": {
      "buckets": [
        {
          "key_as_string": "2001-01-01",
          "key": 978307200000,
          "doc_count": 1
        },
        {
          "key_as_string": "2002-01-01",
          "key": 1009843200000,
          "doc_count": 0
        },
        {
          "key_as_string": "2003-01-01",
          "key": 1041379200000,
          "doc_count": 1
        },
        {
          "key_as_string": "2004-01-01",
          "key": 1072915200000,
          "doc_count": 0
        },
        {
          "key_as_string": "2005-01-01",
          "key": 1104537600000,
          "doc_count": 0
        },
        {
          "key_as_string": "2006-01-01",
          "key": 1136073600000,
          "doc_count": 0
        },
        {
          "key_as_string": "2007-01-01",
          "key": 1167609600000,
          "doc_count": 0
        },
        {
          "key_as_string": "2008-01-01",
          "key": 1199145600000,
          "doc_count": 0
        },
        {
          "key_as_string": "2009-01-01",
          "key": 1230768000000,
          "doc_count": 0
        },
        {
          "key_as_string": "2010-01-01",
          "key": 1262304000000,
          "doc_count": 0
        },
        {
          "key_as_string": "2011-01-01",
          "key": 1293840000000,
          "doc_count": 0
        },
        {
          "key_as_string": "2012-01-01",
          "key": 1325376000000,
          "doc_count": 0
        },
        {
          "key_as_string": "2013-01-01",
          "key": 1356998400000,
          "doc_count": 0
        },
        {
          "key_as_string": "2014-01-01",
          "key": 1388534400000,
          "doc_count": 0
        },
        {
          "key_as_string": "2015-01-01",
          "key": 1420070400000,
          "doc_count": 0
        },
        {
          "key_as_string": "2016-01-01",
          "key": 1451606400000,
          "doc_count": 1
        },
        {
          "key_as_string": "2017-01-01",
          "key": 1483228800000,
          "doc_count": 1
        },
        {
          "key_as_string": "2018-01-01",
          "key": 1514764800000,
          "doc_count": 1
        }
      ]
    }
  }
}

嵌套使用

聚合操作是可以嵌套使用的。通过嵌套,可以使得metric类型的聚合操作作用在每一bucket上。我们可以使用ES的嵌套聚合操作来完成稍微复杂一点的统计功能。

如:统计每年中用户的最高工资

POST /zpark/user/_search
{
  "aggs": {
    "date_histogram": {			# bucket聚合  按照年分区
      "date_histogram": {
        "field": "birthday",
        "interval": "year",
        "format": "yyyy-MM-dd"
      },
      "aggs": {
        "salary_max": {
          "max": {				# metric聚合  求最大工资
            "field": "salary"
          }
        }
      }
    }
  }
}
--------------------------------------------------------------------------------------
{
    ......
    "aggregations": {
    "date_histogram": {
      "buckets": [
        ......
        {
          "key_as_string": "2017-01-01",
          "key": 1483228800000,
          "doc_count": 1,
          "salary_max": {
            "value": 5000
          }
        },
        {
          "key_as_string": "2018-01-01",
          "key": 1514764800000,
          "doc_count": 1,
          "salary_max": {
            "value": 1000
          }
        }
      ]
    }
  }
}

你可能感兴趣的:(Elasticsearch)