前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Elasticsearch入门——搜索与聚合

Elasticsearch入门——搜索与聚合

作者头像
灬沙师弟
发布2024-01-11 14:44:56
1910
发布2024-01-11 14:44:56
举报
文章被收录于专栏:Java面试教程

Elasticsearch作为分布式搜索引擎可以说应用非常广了,可以用于站内搜索,日志查询等功能。本文将着重介绍Elasticsearch的搜索与聚合功能。

Elasticsearch 安装 对于初学者来说Elasticsearch的安装建议采用docker的方式。这里推荐使用docker-compose的方式安装, 里面既包含了Elasticsearch还有Kibana和Cerabro, 可以一键安装到位了。

启动docker之后访问Kibana 地址为http://localhost:5601, 导入Kibana默认提供的三种数据, 然后就可以在Kibana的开发者工具中练习Elasticsearch搜索和聚合的语法了。

搜索

搜索算分

在介绍搜索 DSL (Domain Specific Language) 之前先介绍一下Elasticsearch的搜索算分规则。在ES5之前默认的相关性算分采用TF-IDF,现在采用BM25。

TF-IDF TF(Term Frequency): 检索词在一篇文档中出现的频率。检索词出现的次数除以文档的总字数。IDF (Inverse Document Frequency): 计算方式为 log(全部文档数/检索词出现过的文档总数)

TF-IDF计算公式:

代码语言:javascript
复制
TF(检索词1)* IDF(区块链) + TF(检索词2)* IDF(检索词2)....

本质就是加权求和

BM25 BM25的计算公式如下:

TF-IDF是一种早期的信息检索算法,它基于单词在文档中的频率(TF)和在所有文档中的逆文档频率(IDF)来计算相关性。然而,TF-IDF有一些已知的缺点。例如,它假设所有单词都是独立的,不考虑它们之间的关系。此外,TF-IDF对于长文档可能会有偏好,因为长文档可能包含更多的关键词。

BM25是一种更先进的相关性评分算法,它试图解决TF-IDF的一些问题。BM25考虑了单词的频率,但是对于高频词,它的增长速度会慢于TF-IDF,这可以防止某些单词过度影响评分。此外,BM25还考虑了文档的长度,避免了TF-IDF对长文档的偏好。

Term(词项查询)

如果采用如下方式进行查询会发现返回结果为空,这是因为Elasticsearch 在建立索引的时候会默认对customer_first_name字段进行分词, 分词之后Mary变成了mary因此无法完全匹配到。

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "query": {
    "term": {
      "customer_first_name": {
        "value": "Mary"
      }
    }
  }
}

如果改成如下语句就能完全匹配到了

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "query": {
    "term": {
      "customer_first_name.keyword": {
        "value": "Mary"
      }
    }
  }
}

这是如下图所示, text类型字段ES会默认创建一个keyword字段,通过这个字段去查询就能严格匹配到了。

Query (全文本查询)

Term查询并不会去做分词处理, 基于全文本的查询会。基于全文本的查找包括:Match Query / Match Phrase Query / Query String Query。查询的时候会对输入的查询进行分词,每个词逐个进行底层查询,最后将结果进行合并。并且为每个文档生成一个算分。下面例子中会先对“Low Spherecords”进行分词,比如结果是“low” 和“spherecords”, 然后再分别对这两个单词进行底层搜索。

代码语言:javascript
复制
POST /kibana_sample_data_ecommerce/_search
{
    "query": {
        "match": {
          "manufacturer":{
            "query": "Low Spherecords"
          }
        }
    }
}

Structured Search (结构化搜索)

结构化搜索针对的是日期,布尔类型和数字这些类型。对于文本来说,可能是博客标签,电商网站商品的UPCs码或者其他标识。以日期格式为例可以通过range进行范围查找

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "query": {
    "range": {
      "order_date": {
        "gte": "now-4y"
      }
    }
  }
}

Bool Query (复合查询)

bool 查询是一个或者多个子查询的组合。总有有must,should,must_notfilter四种查询子句。其中前面两种影响算分属于Query Context,后面两个不影响算分,属于Filter Context。下面是一个bool 查询的例子

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "query": {
    "bool": {
      "must": {
        "term": {
          "day_of_week" : "Monday"
          
        }
      },
      "must_not": [
        {
          "range": {
            "taxful_total_price": {
              "lte": 90
            }
          }
        }
      ], 
      "filter": {
        "term": {
          "currency": "EUR"
        }
      },
      "should": [
        {
          "terms": {
            "sku" : ["ZO0549605496", "ZO0299602996"]
          }
        }
      ]
    }
  }
}

子查询可以任意顺序出现,同时可以嵌套多个查询,如果在bool查询中没有must条件,should中必须至少满足一条查询。

单字符串多字段查询

Dis Max Query

Dis max query 可以解决的问题。如下有个例子,分别插入两个文档。

代码语言:javascript
复制
PUT /blogs/_doc/1
{
    "title": "Quick brown rabbits",
    "body":  "Brown rabbits are commonly seen."
}

PUT /blogs/_doc/2
{
    "title": "Keeping pets healthy",
    "body":  "My quick brown fox eats rabbits on a regular basis."
}

用如下两个语法去查询,采用第一种语法文档1排在文档2的前面,采用第二种语法结果正好相反。

代码语言:javascript
复制
POST /blogs/_search
{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}
POST blogs/_search
{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick fox" }},
                { "match": { "body":  "Quick fox" }}
            ]
        }
    }
}

原因是因为第一种should语法在算分的过程中会考虑整体语句匹配的总数。上述例子的中title和body字段是相互竞争的, 不应将分数简单的叠加,而是找到单个最佳匹配字段的评分。Disjunction Max Query 是将任何与任一查询匹配的文档作为结果返回。采用字段上最匹配的评分返回 当然第二种语法如果没有加上tie_breaker参数就可能出现超预期的效果。比如查询“Quick pets”的时候,因为两个文档中的字段匹配分数的最高都是一样的所以,文档1又出现在了文档2的前面。可以通过如下加上tie_breaker参数解决。加上后,其他匹配语句的评分会与tie_breaker相乘 ,然后再与最佳匹配的语句求和。

代码语言:javascript
复制
POST blogs/_search
{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.7
        }
    }
}

Multi-Match

Multi-Match提供了best_fields,most_fields, cross_fields 三种查询类型来应对不同的对字段查询场景。Multi-Match基本语法如下

代码语言:javascript
复制
GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "this is a test", 
      "fields": [ "subject", "message" ] 
    }
  }
}

下面是most_fields的例子,这个例子中, title字段使用english分词器, 然后插入两个文档

代码语言:javascript
复制
PUT /titles
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "english"
      }
    }
  }
}

两篇文档

代码语言:javascript
复制
POST titles/_bulk
{ "index": { "_id": 1 }}
{ "title": "My dog barks" }
{ "index": { "_id": 2 }}
{ "title": "I see a lot of barking dogs on the road " }

使用下面的语法查询会发现文档1排在前面与期望不符,这是因为english分词器会把词性给抹掉掉了, barking 变成了bark , dogs变成了dog,而文档1语句更短所以排在了前面。

代码语言:javascript
复制
GET titles/_search
{
  "query": {
    "match": {
      "title": "barking dogs"
    }
  }
}

解决方法是修改titles的设置,增加子字段并添加standard分词。在查询的时候使用most_fields类型进行搜索。

代码语言:javascript
复制
PUT /titles
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "english",
        "fields": {"std": {"type": "text","analyzer": "standard"}}
      }
    }
GET /titles/_search
{
   "query": {
        "multi_match": {
            "query":  "barking dogs",
            "type":   "most_fields",
            "fields": [ "title", "title.std" ]
        }
    }
}
代码语言:javascript
复制
GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "best_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and" 
    }
  }
}

上面采用best_fields并不适合做跨字段的搜索。因为它的执行逻辑如下,是采用对每个field做operator的匹配。(+first_name:will +first_name:smith) | (+last_name:will +last_name:smith)所有的term必须在一个field中都匹配到。

cross_field可用于跨字段搜索。

代码语言:javascript
复制
GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

它的执行逻辑如下 +(first_name:will last_name:will) +(first_name:smith last_name:smith) 所有的term都至少在一个field中匹配到

聚合

Aggregation作为Search的一部分语法如下:

Metric Aggregation

Metric Aggregation可以用来做一些单值或者多值分析。单值分析比如min, max avg, sum , cardinality。多值分析比如stats, extended stats, percentile, top hits。下面是单值分析的例子:

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "max_tax_total_price": {
      "max": {
        "field": "taxful_total_price"
      }
    }
  }
}

Bucket Aggregation

Bucket aggregation 是按照一定规则把文档分配到不同的桶中,达到分类的目的。Terms Aggregation需要打开fieldata。keyword默认支持, text类型需要在mapping中打开然后才会按照分词之后的结果进行分类。如下这个例子中通过打开category的fieldata从而实现针对category做聚合。

代码语言:javascript
复制
PUT kibana_sample_data_ecommerce/_mapping
{
  "properties" : {
    "category":{
       "type":     "text",
       "fielddata": true
    }
  }
}

GET /kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "category": {
      "terms": {
        "field": "category"
      }
    }
  }
}

下面是嵌套聚合的例子,先根据星期进行分类,然后再根据total_quantity进行降序排列取前三个。

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "day_of_week"
      },
      "aggs":{
      "total_quantity":{
        "top_hits": {
          "size": 3,
          "sort": [{"total_quantity":"desc"}]
        }
      }
    }
    }
  }
}

Pipeline Aggregation

Pipeline就是在一次聚合分析的基础之上再做一次聚合分析。比如下面的语法就是找出平均total_quantity最少的那个星期。buckets_path指定聚合路径,然后再去做一次min_bucket的计算。

代码语言:javascript
复制
GET /kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "day_of_week"
      },
        "aggs":{
        "avg_total_quantity":{
          "avg": {
            "field": "total_quantity"
          }
        }
      }
    },
     "min_quantity":{
      "min_bucket":{
        "buckets_path":"categories>avg_total_quantity"
      }
    }
  }
}

根据Pipeline的分析结果输出到原结果中的位置,可将Pipeline分为两大类:

  • Sibling (结果和现有分析结果同级)
    • Max, min, Avg & Sum Bucket
    • Stats, Extended Status Bucket
    • Percentiles Buckets
  • Parent (结果内嵌到现有的聚合分析结果中)
    • Derivative
    • Cumultive Sum
    • Moving Function (滑动窗口) 当数据分散在不同primary shards上的时候,会出现聚合不准确的情况。可以通过添加show_term_doc_count_error对结果进行分析,同时通过增加shard_size的大小来提高精准度。
代码语言:javascript
复制
GET my_flights/_search
{
  "size": 0,
  "aggs": {
    "weather": {
      "terms": {
        "field":"OriginWeather",
        "size":1,
        "shard_size":1,
        "show_term_doc_count_error":true
      }
    }
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java面试教程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 搜索
    • 搜索算分
      • Term(词项查询)
        • Query (全文本查询)
          • Structured Search (结构化搜索)
            • Bool Query (复合查询)
              • 单字符串多字段查询
                • Dis Max Query
                • Multi-Match
            • 聚合
              • Metric Aggregation
                • Bucket Aggregation
                  • Pipeline Aggregation
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档