ElasticSearch-查询

打印 上一主题 下一主题

主题 861|帖子 861|积分 2583

前言

前面我已经搭建好了ElasticSearch服务,并完成了MySQL到ElasticSearch的数据迁移;
使用ElasticSearch的初衷就是为了大数据搜索,本文将介绍ElaticSearch中各种查询方法;
一、精确查询(termQuery)

termQuery不会对查询条件进行分词 ,但这并不以为着查询的字段没有进行分词存储;
1.Kibana查询

term精确查询并不会对查询条件进行分词,类似于MySQL中 select * from table where 字段='xx值';
  1. GET hotel/_search
  2. {
  3.   "query": {
  4.     "term": {
  5.       "brand": {
  6.         "value": "万豪"
  7.       }
  8.     }
  9.   },
  10.   "from": 0,
  11.   "size": 20
  12. }
复制代码
2.JavaAPI查询

将以上在Kibana输入的DSM转换成Java代码;
  1. //按照品牌精确查询
  2.     @Override
  3.     public Map<String, Object> brandTermQuery(int current, int size, Map<String, Object> searchParam) {
  4.         //按品牌精确查询实现
  5.         //1.获取前端参数
  6.         String brand = (String) searchParam.get("brand");
  7.         //响应前端的Map
  8.         Map<String, Object> resultMap = new HashMap<>();
  9.         //2.构建查询条件
  10.         //查询请求
  11.         SearchRequest hotelSearchRequest = new SearchRequest("hotel");
  12.         //请求体
  13.         SearchSourceBuilder hotelSearchSourceBuilder = new SearchSourceBuilder();
  14.         //如果查询条件为空就查询所有
  15.         if (StringUtils.hasText(brand)) {
  16.             //请求体-查询部分
  17.             TermQueryBuilder hotelTermQueryBuilder = QueryBuilders.termQuery("brand", brand);
  18.             hotelSearchSourceBuilder.query(hotelTermQueryBuilder);
  19.         }
  20.         //请求体-分页部分
  21.         hotelSearchSourceBuilder.from((current - 1) * size);
  22.         hotelSearchSourceBuilder.size(size);
  23.         //查询请求-封装请求体
  24.         hotelSearchRequest.source(hotelSearchSourceBuilder);
  25.         //3.去查询
  26.         try {
  27.             SearchResponse hotelSearchResponse = restHighLevelClient.search(hotelSearchRequest, RequestOptions.DEFAULT);
  28.             //4.处理查询结果集
  29.             SearchHits hotelSearchResponseHits = hotelSearchResponse.getHits();
  30.             //获取命中总条目
  31.             Long totalHotelHits = hotelSearchResponseHits.getTotalHits().value;
  32.             //获取命中的每1个条
  33.             SearchHit[] hoteHits = hotelSearchResponseHits.getHits();
  34.             //前端
  35.             ArrayList<HotelEntity> hotelEntitieList = new ArrayList<>();
  36.             if (hoteHits != null || hoteHits.length > 0) {
  37.                 for (SearchHit hoteHit : hoteHits) {
  38.                     String sourceAsString = hoteHit.getSourceAsString();
  39.                     //字符串转换成Java对象
  40.                     HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);
  41.                     hotelEntitieList.add(hotelEntity);
  42.                 }
  43.             }
  44.             //前端展示
  45.             resultMap.put("list", hotelEntitieList);
  46.             resultMap.put("totalResultSize", totalHotelHits);
  47.             //设置分页相关
  48.             resultMap.put("current", current);
  49.             resultMap.put("totalPage", (totalHotelHits + size - 1) / size);
  50.         } catch (IOException e) {
  51.             throw new RuntimeException(e);
  52.         }
  53.         return resultMap;
  54.     }
复制代码
HotelServiceImpl.java 
二、中文分词器

如果设置了字段的type为keyword,就可以对该字段使用term精确查询;
如果设置了字段的type为text,当添加文档时,对于该域的值会进行分词,形成若干term(词条)存储在倒排索引中。
当用户进行term查询时,ES会将当前查询条件当做1个term(词条),和当前倒排索引中term(词条)进行匹配?
匹配成功则会查询到数据,如果倒排索引中不存在该term(词条)则查询不到数据。
那我们如何对text类型的字段进行term查询呢?
这就需要利用中文分词器对文档中的内容进行中文分词, 重构ES的倒排索引的结构,把整个文档分词成为若干中文term(词条)

1.ElasticSearch内置分词器

在ElasticSearch默认内置了多种分词器:

  • Standard Analyzer - 默认分词器,按英文空格切分
  •  Simple Analyzer - 按照非字母切分(符号被过滤)
  •  Stop Analyzer - 小写处理,停用词过滤(the,a,is)
  •  Whitespace Analyzer - 按照空格切分,不转小写
  • Keyword Analyzer - 不分词,直接将输入当作输出
  • Patter Analyzer - 正则表达式,默认\W+(非字符分割)
 
2.默认分词无法对中文分词

看看ES是默认使用Standard Analyzer分词器对文档内容进行分词;
  1. GET _analyze
  2. {<br>"text": "北京市东城区万豪酒店"
  3. }
复制代码
此时可以发现Standard Analyzer分词器把每1个汉字形成了一个词,这显然无法满足汉语搜索习惯;

 
3.安装IK分词器

IK分词器是国人开发的1款智能中文分词器,基于Java语言开发、具有60万字/秒的高速处理能力。
并且用户可以按需求,设置停用词与扩展词。
3.1.上传IK安装包
  1. #因为启动es时候 已经做好的目录挂载
  2. 容器内部:/usr/share/elasticsearch/plugins
  3. 宿主机:/mydata/elasticsearch/plugins
  4. 所以只需要将文件复制到/mydata/elasticsearch/plugins 目录下即可
复制代码
3.2.重启容器
  1. docker restart elasticsearch
复制代码
 3.3.测试
  1. GET /_analyze
  2. {
  3.   "analyzer": "ik_max_word",
  4.   "text": "北京市东城区万豪酒店"
  5. }
复制代码
 
4.使用IK分词器

4.1.分词模式

IK分词器有两种分词模式it_max_word和ik_smart模式

  • ik_max_word:细粒度分词,存储的时候分词可以细粒度一些;
  • ik_smart:粗粒度分词,搜索的时候可以对搜索条件分词粗粒度一些;
4.2.配置自定义词库

IK分词器虽然非常智能。但是中华语言博大精深,其并不能完全识别所有的词。
如在文档存储时,无法对专业术语、店名、网络词语等等。所以IK提供了扩展词库,用户可以按需求添加自定义分词数据。
4.2.1.定义扩展词

4.2.2.定义停用词

4.3.修改IK分词器的配置文件
  1. vim IKAnalyzer.cfg.xml
  2. #修改配置文件 注意这个地方 不要把搞乱码了!!!
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
  5. <properties>
  6.     <comment>IK Analyzer 扩展配置</comment>
  7.    
  8.     <entry key="ext_dict">my.dic</entry>
  9.      
  10.     <entry key="ext_stopwords">extra_stopword.dic</entry>
  11.    
  12.     <entry key="remote_ext_dict">http://106.75.109.43:28888/remote.dic</entry>
  13.    
  14.    
  15. </properties>
复制代码
 
4.4.重建索引指定分词器

把数据从原索引(表)中迁移到目标索引(表);
如果是线上环境,在重建索引时,一定要选择异步构建和平滑构建; 
  1. PUT hotel_2
  2. {
  3.   "mappings": {
  4.     "properties": {
  5.       "name":{
  6.         "type": "text",
  7.         "analyzer": "ik_max_word",
  8.         "search_analyzer": "ik_smart"
  9.       },
  10.       "address":{
  11.         "type": "text",
  12.         "analyzer": "ik_max_word",
  13.         "search_analyzer": "ik_smart"
  14.       },
  15.       "brand":{
  16.         "type": "keyword"
  17.       },
  18.       "type":{
  19.         "type": "keyword"
  20.       },
  21.        "price":{
  22.         "type": "integer"
  23.       },
  24.       "specs":{
  25.         "type": "keyword"
  26.       },
  27.        "salesVolume":{
  28.         "type": "integer"
  29.       },
  30.       "area":{
  31.         "type": "text",
  32.         "analyzer": "ik_max_word",
  33.         "search_analyzer": "ik_smart"
  34.       },
  35.       "imageUrl":{
  36.         "type": "text"
  37.       },
  38.       "synopsis":{
  39.         "type": "text",
  40.         "analyzer": "ik_max_word",
  41.         "search_analyzer": "ik_smart"
  42.       },
  43.       "createTime":{
  44.         "type": "date",
  45.         "format": "yyyy-MM-dd"
  46.       },
  47.       "isAd":{
  48.         "type":"integer"
  49.       }
  50.     }
  51.   }
  52. }
  53. #重建索引 异步构建和平滑构建
  54. POST _reindex?wait_for_completion=false&requests_per_second=2000
  55. {
  56.   "source": {
  57.     "index": "原始索引名字"
  58.   },
  59.   "dest": {
  60.     "index": "目标索引名字"
  61.   }
  62. }
  63. #查看任务完成情况
  64. GET _tasks/任务id
  65. #重建别名关联关系
  66. #断开原来的关系
  67. POST _aliases
  68. {
  69.   "actions": [
  70.     {
  71.       "remove": {
  72.         "index": "hotel_1",
  73.         "alias": "hotel"
  74.       }
  75.     }
  76.   ]
  77. }
  78. #删除原来的索引表
  79. DELETE hotel_1
  80. #新建hotel_2的关系
  81. POST _aliases
  82. {
  83.   "actions": [
  84.     {
  85.       "add": {
  86.         "index": "hotel_2",
  87.         "alias": "hotel"
  88.       }
  89.     }
  90.   ]
  91. }
复制代码
dms 
4.5.测试文档是否被分词

此时文档在存储时已经被中文分词器进行了中文分词并存储,我们就可以使用termQuery精确查询进行分词结果测试了;
由于termQuery精确查询,不会对查询条件进行分词,所依我根据分词结果进行查询,如果分词成功,就会查询到text字段的结果;
 
三、分词查询(mathQuery)

上述的term精确查询必须要根据分词之后的结果进行精确查询;
可是用户不知道你的文档是怎么分词的,所以我们需要对用户的查询条件也进行分词;
1.Kibana分词查询
  1. GET hotel/_search
  2. {
  3.   "query": {
  4.     "match": {
  5.      "name":"北京市东城区瑞麟湾"
  6.     }
  7.   }
  8. }
复制代码
matchQuery会对查询条件进行分词,并拿分词后的结果,去ES中进行逐一匹配,默认取结果并集。
2.JavaAPI分词查询

  1. //根据酒店名称匹配查询
  2.     @Override
  3.     public Map<String, Object> nameMatchQuery(Integer current, Integer size, Map<String, Object> searchParam) {
  4.         //设置查询
  5.         SearchRequest searchRequest = new SearchRequest("hotel");
  6.         SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  7.         Map<String, Object> map = new HashMap<>();
  8.         //获取name参数
  9.         String name = (String) searchParam.get("name");
  10.         if (StringUtils.hasText(name)) {
  11.             //组装查询对象
  12.             MatchQueryBuilder nameMatchQueryBuilder = QueryBuilders.matchQuery("name", name);
  13.             searchSourceBuilder.query(nameMatchQueryBuilder);
  14.         }
  15.         //设置分页
  16.         searchSourceBuilder.from((current - 1) * size);
  17.         searchSourceBuilder.size(size);
  18.         searchRequest.source(searchSourceBuilder);
  19.         //处理查询结果
  20.         try {
  21.             SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  22.             SearchHits hits = searchResponse.getHits();
  23.             long totalHits = hits.getTotalHits().value;
  24.             SearchHit[] searchHits = hits.getHits();
  25.             List<HotelEntity> list = new ArrayList<>();
  26.             for (SearchHit searchHit : searchHits) {
  27.                 String sourceAsString = searchHit.getSourceAsString();
  28.                 list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
  29.             }
  30.             map.put("list", list);
  31.             map.put("totalResultSize", totalHits);
  32.             map.put("current", current);
  33.             //设置总页数
  34.             map.put("totalPage", (totalHits + size - 1) / size);
  35.         } catch (IOException e) {
  36.             throw new RuntimeException(e);
  37.         }
  38.         return map;
  39.     }
复制代码
HotelServiceImpl.java 
四、模糊搜索(wildcardQuery)

当在搜索框进行搜索时,展示出所有品牌以美开头的酒店。
wildcardQuery:会对查询条件进行分词,还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
1.Kibana分词查询
  1. GET hotel/_search
  2. {
  3.   "query": {
  4.     "wildcard": {
  5.       "brand": {
  6.         "value": "美*"
  7.       }
  8.     }
  9.   }
  10. }
复制代码
2.JavaAPI分词查询

  1. //根据酒店品牌模糊查询
  2.     @Override
  3.     public Map<String, Object> nameWildcardQuery(Integer current, Integer size, Map<String, Object> searchParam) {
  4.         //设置查询
  5.         SearchRequest searchRequest = new SearchRequest("hotel");
  6.         SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  7.         //根据酒店名称模糊查询
  8.         //1.获取前端参数
  9.         String name = (String) searchParam.get("name");
  10.         //2.组装查询对象
  11.         if (StringUtils.hasText(name)) {
  12.             WildcardQueryBuilder brandWildcardQuery = QueryBuilders.wildcardQuery("brand", name+"*");
  13.             searchSourceBuilder.query(brandWildcardQuery);
  14.         }
  15.         //设置分页
  16.         searchSourceBuilder.from((current - 1) * size);
  17.         searchSourceBuilder.size(size);
  18.         searchRequest.source(searchSourceBuilder);
  19.         Map<String, Object> map = new HashMap<>();
  20.         try {
  21.             SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  22.             SearchHits hits = searchResponse.getHits();
  23.             long totalHits = hits.getTotalHits().value;
  24.             SearchHit[] searchHits = hits.getHits();
  25.             List<HotelEntity> list = new ArrayList<>();
  26.             for (SearchHit searchHit : searchHits) {
  27.                 String sourceAsString = searchHit.getSourceAsString();
  28.                 list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
  29.             }
  30.             map.put("list", list);
  31.             map.put("totalResultSize", totalHits);
  32.             map.put("current", current);
  33.             //设置总页数
  34.             map.put("totalPage", (totalHits + size - 1) / size);
  35.         } catch (IOException e) {
  36.             throw new RuntimeException(e);
  37.         }
  38.         return map;
  39.     }
复制代码
HotelServiceImpl.java 
五、多域(字段)查询

当用户在搜索框输入查询条件时,为了给用户展示更多的数据,该条件不应该仅仅作用于某1个域(字段),而要让其作用于多个域进行搜索,从而搜索出更多的查询结果。
类似于MySQL数据中的 select * from table 字段1=条件  or 字段2=条件.....;
简而言之就是使用1个条件去多个字段中查询;
当前需要将用户的搜索条件,作用于:酒店名称(name)、酒店描述(synopsis)、酒店地区(area)、酒店地址(address);
1.Kibana查询
  1. GET hotel/_search
  2. {
  3.   "query": {
  4.     "query_string": {
  5.       "fields": ["name","brand","address","synopsis"],
  6.       "query": "万豪 OR 北京 OR 上海"
  7.     }
  8.   }
  9. }
复制代码
2.JavaAPI查询

  1.     //根据name,synopsis,area,address进行多域(字段)查询
  2.     @Override
  3.     public Map<String, Object> searchQueryStringQuery(Integer current, Integer size, Map<String, Object> searchParam) {
  4.         //设置查询
  5.         SearchRequest searchRequest = new SearchRequest("hotel");
  6.         SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  7.         Map<String, Object> map = new HashMap<>();
  8.         //根据name,synopsis,area,address进行多域查询
  9.         String condition = (String) searchParam.get("condition");
  10.         //组装查询对象
  11.         if (StringUtils.hasText(condition)) {
  12.             QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(condition)
  13.                     .field("name")
  14.                     .field("address")
  15.                     .field("synopsis")
  16.                     .field("area")
  17.                     .defaultOperator(Operator.OR);
  18.             searchSourceBuilder.query(queryStringQueryBuilder);
  19.         }
  20.         //设置分页
  21.         searchSourceBuilder.from((current - 1) * size);
  22.         searchSourceBuilder.size(size);
  23.         searchRequest.source(searchSourceBuilder);
  24.         try {
  25.             SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  26.             SearchHits hits = searchResponse.getHits();
  27.             long totalHits = hits.getTotalHits().value;
  28.             SearchHit[] searchHits = hits.getHits();
  29.             List<HotelEntity> list = new ArrayList<>();
  30.             for (SearchHit searchHit : searchHits) {
  31.                 String sourceAsString = searchHit.getSourceAsString();
  32.                 list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
  33.             }
  34.             map.put("list", list);
  35.             map.put("totalResultSize", totalHits);
  36.             map.put("current", current);
  37.             //设置总页数
  38.             map.put("totalPage", (totalHits + size - 1) / size);
  39.         } catch (IOException e) {
  40.             throw new RuntimeException(e);
  41.         }
  42.         return map;
  43.     }
复制代码
HotelServiceImpl 
六、排序查询(sort)

当用户进行搜索时,有时会关注该商品的销量、评论数等信息,对这些进行进行排序,搜索出销量最高或评论数最多的商品。
1.Kibana查询
  1. #排序查询:支持多字段排序
  2. GET hotel/_search
  3. {
  4.   "query": {
  5.     "match_all": {}
  6.   },
  7.   "sort": [
  8.     {
  9.       "price": {
  10.         "order": "desc"
  11.       }
  12.     },
  13.      {
  14.       "salesVolume": {
  15.         "order": "asc"
  16.       }
  17.     }
  18.   ]
  19. }
复制代码
2.JavaAPI查询

 
 
 
七、范围查询(range)

 
八、多条件查询

 
九、纠错查询(fuzzy)

 
十、高亮展示搜索结果

当用户在搜索框输入搜索条件后,对于查询结果的展示,应将搜索条件以特殊的样式展示,这种查询就称为高亮结果查询;

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
参考
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

尚未崩坏

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表