elasticsearch 搜索引擎工具的高级使用

elasticsearch API文档

聚合

聚合功能可以将数据进行统计,例如:叫小明/小红/小蓝/...的有多少人;叫小明/小红/小蓝/...的人平均年龄是多少

类似于MySQL中的group by,常见的聚合有以下三种:

Bucket:对文档数据分组,并统计每组数量

Metric:对文档数据做计算,例如avg

Pipeline:基于其他聚合结果再做聚合

不过,我们需要注意的是,参与聚合的字段类型无法是被可分词的字段,例如text

GET /lsty/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { #自己取的聚合名称,按照brand字段进行分组就是Bucket聚合
      "terms": {
        "field": "brand",
        "size": 20, 
        "order": {
          "scoreAgg.avg": "desc" #按照子聚合scoreAgg的avg值降序排序
        }
      },
      "aggs": { #brandAgg聚合的子聚合,也就是brandAgg分组后对每组分别计算
        "scoreAgg": { #自己取的聚合名称
          "stats": { #聚合方法,还可以是avg,max等,stats可以计算count,min,max,avg,sum几种信息
            "field": "score" #score为聚合字段
          }
        }
      }
    },
    "priceAgg": { #此处聚合我为了和scoreAgg子聚合做对比,这两聚合都属于Metric聚合
      "stats": {
        "field": "price"
      }
    }
  }
}

 

使用JavaRestClient进行聚合,查找

import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

@SpringBootTest
public class HotelAggTest {

    private RestHighLevelClient client;

    @Test
    void testAggregation() throws IOException {
        SearchRequest request = new SearchRequest("lsty");
        request.source().size(0); //我们只需要聚合数据,无需正常的查询数据,所以size设置0
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg") //terms表示精确查询,brandAgg是对本次聚合取名
                        .field("brand") //对brand字段进行聚合
                        .size(20) //最多读取20条信息
        );

        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        handleResponse(response);


    }

    //输出数据处理
    private static void handleResponse(SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        Terms brandTerms = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();

        for (Terms.Bucket bucket:buckets){
            System.out.println(bucket.getKeyAsString());
        }

    }


    @BeforeEach
    void setUp(){
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("192.168.230.100:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

}

 

 自定义分词器和拼音分词器

为了方便用户体验,除了IK这种中文分词器,我们还会使用pinyin分词器

GitHub拼音分词器网址 

安装方式和IK分词器一样,将文件放入plugins文件夹然后重启ES即可

 

我们需要将IK分词器和pinyin分词器配合使用,所以我们直接自定义分词器,将二者结合

首先我们需要知道,分词器分为三部分:

character filters:对文本进行处理,例如替换,删除文字

tokenizer:对文本进行分词。IK就是这部分

tokenizer filter:将tokenizer分词后的词条进一步处理,大小写转换,拼音处理,同义词处理。pinyin分词器就是这部分

 

我们在创建索引库时配置了自定义分词器,这里我们只设置了tokenizer和tokenizer filter两部分

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": {  #自己取得自定义分词器名
          "tokenizer": "ik_max_word", #配置tokenizer
          "filter": "pinyin" #配置tokenizer filter
        }
      }
    }
  }
}

上述的配置中,pinyin分词器会有些问题,所以我们采用如下的方式,配置pinyin分词器。

有些时候,我们需要建立索引库时和查询关键字时,所用的分词器不一样。

例如因为有同音字的存在,我们不希望在搜索时进行分词,只希望在建立索引库时进行分词。这样在搜索中文时,就不会出现所搜到同音字的情况。

那么我们可以设置索引字段的search_analyzer属性,来调整搜索关键字时的分词器。如果没设置,会使用analyzer属性的分词器,都没设置则使用默认的分词器。

PUT /lsty
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "text_anlyzer": {  #自己取得自定义分词器名
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": { #自定义tokenizer filter
        "py": {  #过滤名称
          "type": "pinyin", #过滤器类型
          "keep_full_pinyin": false, #是否保留每个汉字的完整拼音
          "keep_joined_full_pinyin": true, #是否加入每个词组的完整拼音,例如刘德华——liudehua
          "keep_original": true, #是否保留原始输入
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer", #设置创建索引时使用的分词器
        "search_analyzer": "ik_smart" #设置搜索时使用的分词器
      },
      "address":{
        "type": "keyword",
        "index": false #是否创建索引
      },
      "suggestion":{
        "type": "completion",
        "analyzer": "completion_analyzer" #该字段类型因为是个数组,数组中我用的是brand这种无需分割的元素,所以分词器中的tokenizer属性我们选择keyword
      }
    }
  }
}

自动补全

自动补全的字段有些要求,必须是completion类型,而且字段内容是数组

PUT test #创建索引库
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}
POST test/_doc #插入数据
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}

 自动补全查询

POST /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", #查询的关键字
      "completion": {
        "field": "title", #补全查询的字段
        "skip_duplicates": true, #跳过重复的字段
        "size": 10 #查询前10条数据
      }
    }
  }
}

 

 利用JAVA进行操作

package cn.itcast.hotel;

import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

@SpringBootTest
public class HotelSuggestTest {

    private RestHighLevelClient client;


    @Test
    void testSuggest() throws IOException {
        SearchRequest request = new SearchRequest("lsty"); //查询的索引库
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "hotelSuggestion", //取一个查询名,用于后面取查询结果
                SuggestBuilders.completionSuggestion("suggestion") //suggestion为字段名
                        .prefix("slt") //查询的关键字
                        .skipDuplicates(true) //去除重复信息
                        .size(20)
        ));

        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        
        //处理查询结果
        Suggest suggest = response.getSuggest();
        CompletionSuggestion suggestion = suggest.getSuggestion("hotelSuggestion");

        List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
        for (CompletionSuggestion.Entry.Option option:options){
            System.out.println(option.getText().toString());
        }

    }


    //与ES创建链接
    @BeforeEach
    void setUp(){
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.230.100:9200")
        ));
    }

    //断开与ES的链接
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }


}

 

posted @ 2024-03-18 19:34  凌碎瞳缘  阅读(2)  评论(0编辑  收藏  举报