一、elasticsearch的DSL语法
1、查询语法的分类
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
1) 查询所有:查询出所有数据,一般测试用。例如:match_all。
2)全文检索查询:利用分词器对用户的输入进行分词,然后使用倒排索引。适用语法:
match_query:针对单个条件(字段类型进行匹配),也就是分词的数据匹配一个字段类型
multi_match_query:针对多个条件(字段类型进行匹配),也就是分词的数据匹配多个字段类型。
3)精确查询:根据词条精准查询,一般是不分词的。比如keyword,数值、日期、boolean等类型字段。比如:
ids:查询多个id。
range:范围匹配。
term:精准一对一匹配。
4)地理查询,根据经纬度匹配:
geo_distance:
geo_bounding_box:
5)复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。
bool:多个查询条件进行组合查询。
function_score:针对符合条件的文档进行分数的额外处理,以致于达到查询结果达到自定义的展示顺序。
2、基本语法
查询语句:GET /indexName/_search
1)查询所有 match_all
2)全文检索查询
a、match_query:通过分词进行查找
一般是根据单个字段类型来进行查找,所以一般查找的都是复合字段(all)。
b、multi_match:根据多个字段查询,参与查询字段越多,查询性能越差。
3)精确查找
定义:精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。
a、term:根据词条精确值查询
b、range:根据值的范围查询
gte --- 大于等于 gt ---大于
lte --- 小于等于 lt---小于
4)地理查询
根据经纬度查询。
常见的使用场景包括: 携程:搜索我附近的酒店 滴滴:搜索我附近的出租车 微信:搜索我附近的人。
geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档也就是选择两个点(对角线关系),根据这两个点得到一个矩形。
geo_distance:查询到指定中心点小于某个距离值的所有文档
5)复合查询
由于某个搜索需要符合多个条件,这个时候就需要将我们上述的搜索条件进行一次拼接查询。
复合查询 Boolean Query,布尔查询是一个或多个查询子句的组合。
子查询的组合方式有:
must:必须匹配每个子查询,类似“与”
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分,类似“非”
filter:必须匹配,不参与算分
上述语句含义就是:城市是上海,且酒店是皇冠假日或者华美达的。价格大于500,并且评分要大于45。
案例:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。
6)fuction score:算分函数查询
算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价等。
a、相关性算分
elasticsearch中的相关性打分算法是什么?
TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大。
BM25:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲线会趋于水平。
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。
例如,我们搜索 "虹桥如家",结果如下:
根据结果我们可以看到,越接近查询条件的词条,分数越高,排名越高。
这个分数我们可以人为自定义控制,将一些我们需要的数据分数提高,从而使得其排名提高,达到我们的自定义展示效果。
使用 function score query,可以修改文档的相关性算分(query score),根据新得到的算分排序。
解释:
"function_score" : 表示的是这个是一个算分查询
"query":查询条件,第一次筛选出符合条件的初始数据。
"functions":表示进行算分操作。
"filter":根据筛选条件得到需要进行算分的数据。
"weight":给一个常量,作为函数结果。
"boost_mode":定义function score(算分结果)和query score(查询结果)的运算方式。
案例:给“如家”这个品牌的酒店排名靠前一些
分析:1)找到所有的酒店 2)是如家酒店的加上10
如果字段类型是text,就无法使用 term这些精确匹配。否则查询为空。
function score query定义的三要素是什么?
过滤条件:哪些文档要加分
算分函数:如何计算function score
加权方式:function score 与 query score如何运算 "boost_mode"
7)搜索结果处理
1、排序
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
案例:给价格进行升序排序,评分降序排序
2、分页
深度分页问题
ES是分布式的,所以会面临深度分页问题。例如按price排序后,获取from = 990,size =10的数据:
1、首先在每个数据分片上都排序并查询前1000条文档。
2、然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档。
3、最后从这1000条中,选取从990开始的10条文档。
如果搜索页数过深,或者结果集(from + size)越大,对内存和CPU的消耗也越高。因此ES设定结果集查询的上限是10000。
from+size 分页
优点:支持随机翻页
缺点:深度分页问题,默认查询上限(from + size)是10000
场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
after search 分页
优点:没有查询上限(单次查询的size不超过10000)
缺点:只能向后逐页查询,不支持随机翻页
场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
scroll 分页
优点:没有查询上限(单次查询的size不超过10000)
缺点:会有额外内存消耗,并且搜索结果是非实时的
场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
3、高亮
3、代码实战
1) match 简单查询
package com.hihonor.controller;
import com.alibaba.fastjson.JSONObject;
import com.hihonor.bean.Hotel;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("elastic/dsl")
public class ElasticDslController {
@Autowired
private RestHighLevelClient restHighLevelClient;
@PostMapping("/match")
public void matchAll() throws IOException {
// 指定查询的索引
SearchRequest request = new SearchRequest("myhotel");
// 查询所有
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
// 执行查询语句
request.source().query(matchAllQuery);
SearchResponse responseMatchAll = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseMatchAll, 0);
// 查询单个字段
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "如家");
// 执行查询语句
request.source().query(matchQuery);
SearchResponse responseMatch = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseMatch, 0);
// 查询多个字段
MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery("上海如家", "city", "brand");
// 执行查询语句
request.source().query(multiMatchQuery);
SearchResponse responseMultiMatch = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseMultiMatch, 0);
// term精确查询
TermQueryBuilder termQuery = QueryBuilders.termQuery("id", "36934");
request.source().query(termQuery);
SearchResponse responseTerm = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseTerm, 0);
// 范围查询
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price").gte(336);
request.source().query(rangeQuery);
SearchResponse responseRange = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseRange, 0);
}
/**
* 查询结果进行展示
*
* @param responseMatchAll 查询得到的结果
*/
private void showSearchString(SearchResponse responseMatchAll, int highLight) {
System.out.println("查询的数据个数是"+responseMatchAll.getHits().getTotalHits());
SearchHit[] hits = responseMatchAll.getHits().getHits();
for (SearchHit hit : hits) {
Hotel hotel = JSONObject.parseObject(hit.getSourceAsString(), Hotel.class);
// highLight为1,进行一下高亮处理展示
if (highLight == 1) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (CollectionUtils.isEmpty(highlightFields)) {
continue;
}
HighlightField highlightField = highlightFields.get("name");
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
System.out.println("高亮展示"+hotel.toString());
continue;
}
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
}
}
2)boolQuery---复合查询
package com.hihonor.controller;
import com.alibaba.fastjson.JSONObject;
import com.hihonor.bean.Hotel;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("elastic/dsl")
public class ElasticDslController {
@Autowired
private RestHighLevelClient restHighLevelClient;
// 复合查询
@PostMapping("/boolQuery")
public void boolQuery() throws IOException {
SearchRequest request = new SearchRequest("myhotel");
// 复合条件查询
// 搜索名字包含“如家”,价格不高于400的酒店。
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 搜索名字包含“如家”
boolQueryBuilder.must(QueryBuilders.matchQuery("name", "如家"));
// 价格不高于400
boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("price").gt(400));
request.source().query(boolQueryBuilder);
// 按照评分降序
request.source().sort("score", SortOrder.DESC);
// 分页 查询前 10 条数据
request.source().from(0).size(10);
SearchResponse boolQuery =
restHighLevelClient.search(request,RequestOptions.DEFAULT);
showSearchString(boolQuery, 0);
}
/**
* 查询结果进行展示
*
* @param responseMatchAll 查询得到的结果
*/
private void showSearchString(SearchResponse responseMatchAll, int highLight) {
System.out.println("查询的数据个数是"+responseMatchAll.getHits().getTotalHits());
SearchHit[] hits = responseMatchAll.getHits().getHits();
for (SearchHit hit : hits) {
Hotel hotel = JSONObject.parseObject(hit.getSourceAsString(), Hotel.class);
// highLight为1,进行一下高亮处理展示
if (highLight == 1) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (CollectionUtils.isEmpty(highlightFields)) {
continue;
}
HighlightField highlightField = highlightFields.get("name");
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
System.out.println("高亮展示"+hotel.toString());
continue;
}
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
}
}
3)functionQuery 算分查询
package com.hihonor.controller;
import com.alibaba.fastjson.JSONObject;
import com.hihonor.bean.Hotel;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("elastic/dsl")
public class ElasticDslController {
@Autowired
private RestHighLevelClient restHighLevelClient;
// 算分查询 查询 名字是 如家,id是36934的分数加上30
@PostMapping("/functionQuery")
public void functionQuery() throws IOException {
SearchRequest request = new SearchRequest("myhotel");
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("name", "如家"));
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders
.functionScoreQuery(
boolQueryBuilder,
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("id", "441836"), ScoreFunctionBuilders.weightFactorFunction(30))
});
request.source().query(functionScoreQuery);
SearchResponse functionResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(functionResponse, 0);
}
/**
* 查询结果进行展示
*
* @param responseMatchAll 查询得到的结果
*/
private void showSearchString(SearchResponse responseMatchAll, int highLight) {
System.out.println("查询的数据个数是"+responseMatchAll.getHits().getTotalHits());
SearchHit[] hits = responseMatchAll.getHits().getHits();
for (SearchHit hit : hits) {
Hotel hotel = JSONObject.parseObject(hit.getSourceAsString(), Hotel.class);
// highLight为1,进行一下高亮处理展示
if (highLight == 1) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (CollectionUtils.isEmpty(highlightFields)) {
continue;
}
HighlightField highlightField = highlightFields.get("name");
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
System.out.println("高亮展示"+hotel.toString());
continue;
}
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
}
}
4)高亮查询展示
package com.hihonor.controller;
import com.alibaba.fastjson.JSONObject;
import com.hihonor.bean.Hotel;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("elastic/dsl")
public class ElasticDslController {
@Autowired
private RestHighLevelClient restHighLevelClient;
// 高亮查询
@PostMapping("/matchHighLight")
public void matchHighLight() throws IOException {
// 指定查询的索引
SearchRequest request = new SearchRequest("myhotel");
// term精确查询
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "如家");
request.source().query(matchQuery);
// 高亮的语句 高亮字段是哪个 是否需要和拆线呢字段匹配(也就是本方法内的id)
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
SearchResponse responseTerm = restHighLevelClient.search(request, RequestOptions.DEFAULT);
showSearchString(responseTerm, 1);
}
/**
* 查询结果进行展示
*
* @param responseMatchAll 查询得到的结果
*/
private void showSearchString(SearchResponse responseMatchAll, int highLight) {
System.out.println("查询的数据个数是"+responseMatchAll.getHits().getTotalHits());
SearchHit[] hits = responseMatchAll.getHits().getHits();
for (SearchHit hit : hits) {
Hotel hotel = JSONObject.parseObject(hit.getSourceAsString(), Hotel.class);
// highLight为1,进行一下高亮处理展示
if (highLight == 1) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (CollectionUtils.isEmpty(highlightFields)) {
continue;
}
HighlightField highlightField = highlightFields.get("name");
String name = highlightField.getFragments()[0].toString();
hotel.setName(name);
System.out.println("高亮展示"+hotel.toString());
continue;
}
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
}
}