• Java修仙传之神奇的ES2(巧妙的查询及结果处理篇)


    SDL语句查询

    查询的基本语法

    1. GET /indexName/_search
    2. {
    3. "query": {
    4. "查询类型": {
    5. "查询条件": "条件值"
    6. }
    7. }
    8. }

    根据文档id查询

    1. #查询文档
    2. GET hotel/_doc/36934

    查询所有

    1. 会弹出该索引库下所有文档// 查询所有
    2. GET /indexName/_search
    3. {
    4. "query": {
    5. "match_all": {
    6. }
    7. }
    8. }

    全文检索查询(搜索框)

    参与搜索的字段必须是可分词的text类型的字段。

    1. 利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
    2. - match_query
    3. - multi_match_query
    1. - 对用户搜索的内容做分词,得到词条
    2. - 根据词条去倒排索引库中匹配,得到文档id
    3. - 根据文档id找到文档,返回给用户
    1. - 商城的输入框搜索
    2. - 百度输入框搜索

    单字段查询(match查询)

    1. GET /indexName/_search
    2. {
    3. "query": {
    4. "match": {
    5. "FIELD": "TEXT"
    6. }
    7. }
    8. }

    多字段查询

    备注:字段必须是text类型,可以分词类型!!!!

    查找keyword、数值、日期、boolean等会报错!!!

    如果放入精确类型的字段,会报错!!!!!

    1. GET /indexName/_search
    2. {
    3.   "query": {
    4.     "multi_match": {
    5.       "query""TEXT",
    6.       "fields": ["FIELD1"" FIELD12"]
    7.     }
    8.   }
    9. }
    10. GET /hotel/_search
    11. {
    12. "query": {
    13. "multi_match": {
    14. "query": "外滩豫园如家四川四平",
    15. "fields": ["brand","name","business"]
    16. }
    17. }
    18. }

    精准查询

    精确查询一般是查找keyword、数值、日期、boolean等类型字段(非text)。所以不会对搜索条件分词。

    term查询(精确查询)

    查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。

    如果输入1234,会完全匹配1234,

    123,12345,12,1等都无法匹配到

    1. GET /indexName/_search
    2. {
    3.   "query": {
    4.     "term": {
    5.       "FIELD": {
    6.         "value""VALUE"
    7.       }
    8.     }
    9.   }
    10. }
    11. # term查询
    12. GET /hotel/_search
    13. {
    14. "query": {
    15. "term": {
    16. "business": {
    17. "value": "豫园"
    18. }
    19. }
    20. }
    21. }

    range查询(范围查询(数值用))

    范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。

    1. // range查询
    2. GET /indexName/_search
    3. {
    4.   "query": {
    5.     "range": {
    6.       "FIELD": {
    7.         "gte"10, // 这里的gte代表大于等于,gt则代表大于
    8.         "lte"20 // lte代表小于等于,lt则代表小于
    9.       }
    10.     }
    11.   }
    12. }

    地理坐标查询

    所谓的地理坐标查询,其实就是根据经纬度查询

    矩形范围查询

    查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点。

    1. GET hotel/_search
    2. {
    3. "query":{
    4. "geo_bounding_box":{
    5. "location":{
    6. "top_left": {
    7. "lat": 31.1,
    8. "lon": 121.5
    9. },
    10. "bottom_right":{
    11. "lat": 30.9,
    12. "lon": 121.7
    13. }
    14. }
    15. }
    16. }
    17. }

    附近查询

    附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。

    1. GET /indexName/_search
    2. {
    3.   "query": {
    4.     "geo_distance": {
    5.       "distance""15km", // 半径
    6.       "FIELD""31.21,121.5" // 圆心
    7.     }
    8.   }
    9. }

    复合查询

    复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:

    • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
    • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索

    _scorc算分机制

    当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

    决定性因素:词条在文档中出现的次数。

    比如:10个词条,其中5个是目标词条,得分肯定高了

    10个词条,其中1个是目标词条,得分肯定低了

    在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:

    1. 改进的原因:
    2. 早期版本:分数取决于词条出现次数。出现次数越高,得分越高
    3. 目前版本:分数取决于词条出现次数。出现次数越高,得分越高,但是会根据算法得到一个上线,不会特别的高

    算分函数查询

    1. function score 查询中包含四部分内容:
    2. - 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
    3. - 过滤条件:filter部分,符合该条件的文档才会重新算分
    4. - 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    5. - weight:函数结果是常量
    6. - field_value_factor:以文档中的某个字段值作为函数结果
    7. - random_score:以随机数作为函数结果
    8. - script_score:自定义算分函数算法
    9. - 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    10. - multiply:相乘
    11. - replace:用function score替换query score
    12. - 其它,例如:sum、avg、max、min
    13. function score的运行流程如下:
    14. - 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
    15. - 2)根据过滤条件,过滤文档
    16. - 3)符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
    17. - 4)将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。
    18. 因此,其中的关键点是:
    19. - 过滤条件:决定哪些文档的算分被修改
    20. - 算分函数:决定函数算分的算法
    21. - 运算模式:决定最终算分结果
    1. # 算分函数查询
    2. GET hotel/_search
    3. {
    4. "query": {
    5. "function_score": {
    6. "query": {
    7. "match": {
    8. "city": "上海"
    9. }
    10. },
    11. "functions": [
    12. {
    13. "filter": {
    14. "term": {
    15. "business": "豫园"
    16. }
    17. },
    18. "weight": 10
    19. }
    20. ],
    21. "boost_mode": "replace"
    22. }
    23. }
    24. }

    布尔查询

    (打分的字段越多,查询的性能也越差,所以适当使用filter)

    布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

    • must:必须匹配每个子查询,类似“与”
    • should:选择性匹配子查询,类似“或”
    • must_not:必须不匹配,不参与算分,类似“非”
    • filter:必须匹配,不参与算分
    1. - 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
    2. - 其它过滤条件,采用filter查询。不参与算分
    1. GET /hotel/_search
    2. {
    3.   "query": {
    4.     "bool": {
    5.       "must": [
    6.         {"term": {"city""上海" }}
    7.       ],
    8.       "should": [
    9.         {"term": {"brand""皇冠假日" }},
    10. {"term": {"brand""华美达" }}
    11.       ],
    12.       "must_not": [
    13.         { "range": { "price": { "lte"500 } }}
    14.       ],
    15.       "filter": [
    16.         { "range": {"score": { "gte"45 } }}
    17.       ]
    18.     }
    19.   }
    20. }

    排序

    keyword、数值、日期类型好排

    text待测试

    1. GET /indexName/_search
    2. {
    3.   "query": {
    4.     "match_all": {}
    5.   },
    6.   "sort": [
    7.     {
    8.       "FIELD""desc"  // 排序字段、排序方式ASC、DESC
    9.     }
    10.   ]
    11. }

    分页

    基本分页:

    基本逻辑:

    查询100-110条,共10条数据

    1:先读取到100条

    2:再往后读10条,到110

    3:获取100-110条,这10条数据

    当超过10000条,效率无比低下。不支持10000条以上的查询

    1. GET /hotel/_search
    2. {
    3.   "query": {
    4.     "match_all": {}
    5.   },
    6.   "from"0// 分页开始的位置,默认为0
    7.   "size"10// 期望获取的文档总数
    8.   "sort": [
    9.     {"price""asc"}
    10.   ]
    11. }

    深度分页

    问题一:同上

    问题二:问题一的扩展版。当集群之后,如果操作集群中的数据,则需要先读取整个集群,再进行操作。

    此时每个节点,都会读取大量数据,然后汇总,处理

    A节点,读10000条,向下取10条

    B节点同理

    最后:所有节点的10条汇总,取前N条。执行了多次查询

    1. GET hotel/_search
    2. {
    3. "query": {
    4. "match": {
    5. "all": "外滩如家"
    6. }
    7. },
    8. "size": 3,
    9. "search_after": [379, "433576"],
    10. "sort": [
    11. {
    12. "price": {
    13. "order": "desc"
    14. }
    15. },
    16. {
    17. "id": {
    18. "order": "asc"
    19. }
    20. }
    21. ]
    22. }

    search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

    核心:基于分页取值

    高亮(关键字加标签)

    高亮显示的实现分为两步:

    • 1)给文档中的所有关键字都添加一个标签,例如标签
    • 2)页面给标签编写CSS样式

    高亮的核心:关键字加标签

    • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
    • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
    • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
    1. GET /hotel/_search
    2. {
    3. "query": {
    4. "match": {
    5. "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    6. }
    7. },
    8. "highlight": {
    9. "fields": { // 指定要高亮的字段
    10. "FIELD": {
    11. "pre_tags": "", // 用来标记高亮字段的前置标签
    12. "post_tags": "" // 用来标记高亮字段的后置标签
    13. }
    14. }
    15. }
    16. }

    JAVA客户端查询并解析

    查询全部matchAllQuery

    1:组装查询,发起请求()

    1.1:request.source():根据需求点出来需要排序分页还是什么

    2:根据结构,层层解析

    2.1:根据结构解析

    2.2:返回数据为json,可以转java类等操作

    1. @Test
    2. void testMatchAll() throws IOException {
    3. // 1.准备Request
    4. SearchRequest request = new SearchRequest("hotel");
    5. // 2.准备DSL
    6. request.source()
    7. .query(QueryBuilders.matchAllQuery());
    8. // 3.发送请求
    9. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    10. // 4.解析响应
    11. handleResponse(response);
    12. }
    13. private void handleResponse(SearchResponse response) {
    14. // 4.解析响应
    15. SearchHits searchHits = response.getHits();
    16. // 4.1.获取总条数
    17. long total = searchHits.getTotalHits().value;
    18. System.out.println("共搜索到" + total + "条数据");
    19. // 4.2.文档数组
    20. SearchHit[] hits = searchHits.getHits();
    21. // 4.3.遍历
    22. for (SearchHit hit : hits) {
    23. // 获取文档source
    24. String json = hit.getSourceAsString();
    25. // 反序列化
    26. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    27. System.out.println("hotelDoc = " + hotelDoc);
    28. }
    29. }

    match查询

    1. @Test
    2. void testMatch() throws IOException {
    3. SearchRequest request = new SearchRequest("hotel");
    4. //单字段查询
    5. request.source().query(QueryBuilders.matchQuery("all", "如家"));
    6. //多字段查询
    7. // request.source().query(QueryBuilders.multiMatchQuery("外滩", "name","brand","business"));
    8. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    9. handleResponse(response);
    10. }

    单字段查询

    多字段查询

    精确查询及范围查询

    1. @Test
    2. void termQuery() throws IOException {
    3. SearchRequest request = new SearchRequest("hotel");
    4. //精确查询
    5. //request.source().query(QueryBuilders.termQuery("city", "上海"));
    6. //范围查询
    7. request.source().query(QueryBuilders.rangeQuery("price").gte(0).lte(1000));
    8. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    9. handleResponse(response);
    10. }

    布尔查询

    1. //布尔查询
    2. @Test
    3. void boolQuery() throws IOException {
    4. SearchRequest request = new SearchRequest("hotel");
    5. //布尔查询
    6. request.source().query(QueryBuilders.boolQuery()
    7. //必须匹配,且参与算分。城市=上海
    8. .must(QueryBuilders.termQuery("city", "上海"))
    9. //必须不匹配 品牌 != 如家
    10. .mustNot(QueryBuilders.termQuery("brand", "如家"))
    11. //必须匹配,但不参与算分。价格>=0,<=1000
    12. .filter(QueryBuilders.rangeQuery("price").gte(0).lte(1000))
    13. );
    14. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    15. handleResponse(response);
    16. }

    分页,排序

    1. @Test
    2. void sortAndPage() throws IOException {
    3. // 页码,每页大小
    4. int page = 2, size = 5;
    5. //备注:这里什么查询条件都没写,所以会查询出所有数据。但是下文分,所以只会取5-10条数据
    6. SearchRequest request = new SearchRequest("hotel");
    7. //分页 这里是写死的
    8. request.source().from((page - 1) * size).size(size);
    9. //排序 升序排序
    10. request.source().sort("price", SortOrder.ASC);
    11. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    12. handleResponse(response);
    13. }

    handleResponse

    1. private void handleResponse(SearchResponse response) {
    2. // 4.解析响应
    3. SearchHits searchHits = response.getHits();
    4. // 4.1.获取总条数
    5. long total = searchHits.getTotalHits().value;
    6. System.out.println("共搜索到" + total + "条数据");
    7. // 4.2.文档数组
    8. SearchHit[] hits = searchHits.getHits();
    9. // 4.3.遍历
    10. for (SearchHit hit : hits) {
    11. // 获取文档source
    12. String json = hit.getSourceAsString();
    13. // 反序列化
    14. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    15. System.out.println("hotelDoc = " + hotelDoc);
    16. }
    17. }

    高亮

    高亮查询

    1. @Test
    2. void highlightQuery() throws IOException {
    3. SearchRequest request = new SearchRequest("hotel");
    4. //拼装高亮信息
    5. HighlightBuilder hb = new HighlightBuilder();
    6. hb.field("name"); //高亮字段
    7. hb.preTags(""); // 字段前缀标签
    8. hb.postTags(""); // 字段后缀标签
    9. hb.requireFieldMatch(false); // 条件和高亮字段可以不一致
    10. //组装查询
    11. request.source().query(QueryBuilders.matchQuery("name", "北京"))
    12. .highlighter(hb);
    13. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    14. //高亮解析
    15. highlightHandel(response);
    16. }

    高亮解析

    1. private void highlightHandel(SearchResponse response) {
    2. // 4.解析响应
    3. SearchHits searchHits = response.getHits();
    4. //获取总条数
    5. Long total = searchHits.getTotalHits().value;
    6. //文档数组
    7. SearchHit[] hits = searchHits.getHits();
    8. //一条文档 hit
    9. for (SearchHit hit : hits) {
    10. //一条原始文档数据
    11. String json = hit.getSourceAsString();
    12. // 反序列化
    13. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    14. //获取高亮结果 String = 字段名 HighlightField = 被高亮的结果
    15. //highlightFields = {name=[name], fragments[[<em>北京</em>希尔顿酒店]]} 多条这种数据
    16. Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    17. //根据字段名称获取高亮结果
    18. if (!CollectionUtils.isEmpty(highlightFields)) {
    19. // 根据字段名获取高亮结果
    20. HighlightField highlightField = highlightFields.get("name");
    21. String result = highlightField.getFragments()[0].string();
    22. //result是所有高亮的信息,我们这里是业务需求,覆盖掉原文的非高亮部分
    23. hotelDoc.setName(result);
    24. }
    25. }
    26. }

    详细解释:

  • 相关阅读:
    使用xlwings获取excel表的行和列以及指定单元格的值
    QT:绘图
    【Python 实战基础】Pandas如何使用日期和随机数生成表格数据类型
    Modelsim无法生成LICENSE的问题
    设计模式篇(Java):装饰者模式
    一起来探究@Schedule定时任务在分布式产生的问题
    nmake简介
    C++单例模式与工厂模式
    21天经典算法之折半插入排序
    20天深度复习JavaSE的详细笔记(十二)——集合(Collection、数据结构、List、泛型深入)
  • 原文地址:https://blog.csdn.net/weixin_50914566/article/details/134296234