DSL查询文档
DSL查询分类
全文查询、分词查询、非分词查询、地理坐标查询、组合查询
match_all 查询所有,不需要查询条件,固定写法_search
第一个hits就是命中的数据 ,total就是条数,第二个hits是source嘞
全文检索查询
我们不要整多个字段查询,参与的字段越多,查询速度越慢,如果有多个字段,可以把这几个字段copy_to到一个字段去查,如all,第一种效查询效率更高
query查询条件text就是输入的内容,比如”酒店“,fields就是字段名字
精确查询
term确保搜索的内容,和文档的内容一模一样
范围range:gte是大于等于,let小于等于,
如果把e去掉,就是大于不等于,或小于不等于
地理坐标查询
第一种查询
top_left:是一个点,左上角的点
bottom_right :也是一个点,右下角的点
两个点形成一个矩形,矩形范围内的就是文档能查出来的数据,比如打车,在我这个圆圈只内的车
适合查一定范围内的信息,比如说按照地图找房子,找酒店等
矩形查询的比较少,一般都是一个圆圈
第二种查询
field:中心点,有点类似于我的位置,或者家庭住址的位置等,然后周边扩撒
15km,就是以field的中心点画一个半径,根据半径画一个圆,这个圆圈起来的所有的点,就是符合的文档
location :是根据中心点(可以自己定位,我的位置)查询在文档符合条件的location的经纬度
字段location:是根据这个点,找文档里在这个距离内符合条件的数据
以上查询都叫简单查询
组合查询,也叫复合查询
人工对搜索排名实现干预,比如百度搜索,我给钱多,就排第一
过滤条件,决定那些文档需要加分
算分文档三要素
- 1、那些文档要加分
- 2、算分函数是啥
- 3、加权模式是啥
查询可以知道外滩的如家分数最低,才4.0分
比如说我想让如家跑到第一名去,查询的时候就加上过滤条件算分函数,和算法,给他加了20分,已经是24分了
注意,这里是查询,实际上并不会修改文档,只是在查询的结果集上给分数加了点数而已
组合查询
案例:价格不高于,就是低于,可以取反
dsl语法,注意,放到mast或should里边都会影响算分的,参与算分的越多,性能越差
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "如家"
}
}
],
"filter": [
{
"geo_distance": {
"distance": "10km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
]
}
}
}
将来我们关键字搜索尽量放到must里,其他的尽量放到must_not、filter里
搜索结果处理
排序
ES默认是根据算分来排序的,分值越高,排序越靠前,但是有时候我们还得按照别的来排序,比如价值等
一旦自己指定排序字段,ES就会放弃打分,这样查询的效率方免也会有一定的提升
keyword类型是字符串,它的排序是按照字母顺序排序,用的比较少
地理坐标排序,比如说到我距离最近的点,做一个升序排序
注意:查询和排序是同级的关系,不是写在query里了
sort排序,就相当于mysql里的orderBy,如果有多个排序,先按照第一个字段排序,第一个字段相等就按照第二个字段排序,和mysql一样
地理坐标排序 unit就是排序后的结果,是按照km展示,还是m展示
ppt写法有点不优雅,直接 field:asc 更优雅 ,先按照评分降序,评分相等就按照价格升序
获取鼠标点击经纬度-地图属性-示例中心-JS API 2.0 示例 | 高德地图API
这个酒店距离我们1278公里,注意,一旦做了排序,_score就是null,就是不会去做相关性算分了
分页
ES底层有一个默认的分页参数,默认10条
form,size类似mysql的limit和row,从哪开始,几行,几条
这是单机的情况非集群模式,想要查出后10条数据,是截取的,先查出1000条数据,然后截取后10条数据
size不配置的话默认是10 ,这是第一页,第二页from就是10,第三页就是20
一旦做集群,ES就会把数据拆分,放到不同的服务里,拆分出的每一份我们叫做分片 ,分片里的数据是不一样的,一旦做了集群,我们不能确定这10条数据是哪个片上的
就是说shard1片1上的前1000,在片2上的不一定就排名还是前1千了
做法应该是把所有片上的前1000名都取出来,然后再排序取出前1千名,然后再截取
上千台服务器,查询前1000条数据,那就是1000*1000,然后排序,数量太大了
不能超过1万条,可以是1万条
一般我们就用from size就行,可以支持随机翻页,向前向后翻页,
search after查询超过1万条,不支持随机翻页,不能像前翻页
高亮
其实就是我们搜索的数据,在页面中给加上一个标签,就是高亮了
就是服务端给搜索的关键字给个标签
前端拿到这个标签给个样式就好了
注意:高亮,一定要对关键字高亮,不能用matchall,一定要带上关键字
fields字段可以有多个,比如说标题、文档内容都要高亮,
ES中,默认标签就是em可以不指定
查询并没有高亮,原因是:默认情况下ES的搜索字段,必须与高亮字段一致,否则不会高亮
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}
但是我们现在就要用all来查询,有个字段,需不需要高亮匹配,默认是需要true改成false就可以了
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"require_field_match": "false"
}
}
}
}
都是同级的,和在一起是一个dsl的json风格语句
RestClient查询文档
快速入门
source里边封装了各种api,如查询,分页,高亮等等
QueryBulder是个接口,我们用QueryBuilders工具类,里边封装了各种查询,term、match、等
解析 ,其实就是逐层解析json,用代码实现
getTotalHits拿到的是对象,点它的value属性,就拿到值嘞
@Test
void esTest1() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.matchAllQuery());
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits responseHits = response.getHits();
long total = responseHits.getTotalHits().value;
System.out.println("查询总条数为:"+ total+"........");
SearchHit[] hits = responseHits.getHits();
for (SearchHit hit : hits) {
// 获取的事json格式的字符串
String s = hit.getSourceAsString();
// w我们可以把json转成对象操作
HotelDoc hotelDoc = JSON.parseObject(s, HotelDoc.class);
System.out.println(hotelDoc);
}
}
全文检索查询
match查询
精确查询
复合查询
和match_all相比只有内部,查询的类型和条件有所差异
我们发现有一部分解析的代码都一样,那么就可以抽取啦,快捷键command+option+m
@Test
void esTest1() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.multiMatchQuery("如家","name", "brand"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
SearchHits responseHits = response.getHits();
long total = responseHits.getTotalHits().value;
System.out.println("查询总条数为:"+ total+"........");
SearchHit[] hits = responseHits.getHits();
for (SearchHit hit : hits) {
// 获取的事json格式的字符串
String s = hit.getSourceAsString();
// w我们可以把json转成对象操作
HotelDoc hotelDoc = JSON.parseObject(s, HotelDoc.class);
System.out.println(hotelDoc);
}
}
演示boolquery
也就是说无论是boolquery、match、term、range。。查询,全都是用queryBuilders,而sorce返回的api是查询、分页、高亮等,是和query查询同级的
private void handleResponse(SearchResponse response) {
SearchHits responseHits = response.getHits();
long total = responseHits.getTotalHits().value;
System.out.println("查询总条数为:"+ total+"........");
SearchHit[] hits = responseHits.getHits();
for (SearchHit hit : hits) {
// 获取的事json格式的字符串
String s = hit.getSourceAsString();
// w我们可以把json转成对象操作
HotelDoc hotelDoc = JSON.parseObject(s, HotelDoc.class);
System.out.println(hotelDoc);
}
}
@Test
void boolQueryTest() throws IOException {
SearchRequest request = new SearchRequest("hotel");
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("name", "如家"));
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
handleResponse(response);
}
排序、分页、高亮
这里用链式编程只用写一个query,不然得下三个query查询
这么写多麻烦不是 ,模拟分页
高亮显示,注意高亮要对关键字高亮,不能用match_all
运行发现并没有高亮,这是因为高亮结果是与原始文档分离的,原始文档不可更改,高亮结果是与source同级的
获取soruce是用hit来获取的,同样获取高亮也用hit,获得结果是map,因为获取结果是个json对象,有key,有value,对应java就是map
将来我们就可以根据key去取value 了
取的高亮以后还要获取数组这一部分,就是value
fragment片段,其实就是取拿数组
拿到之后,就可以把结果set到我们的doc对象里了
value.getFragments()[0] 返回的其实是一个文本 text,它有一个方法string(),讲文本转成字符串
判断集合空spring里边是有工具类的CollectionUtils
private void handleResponse(SearchResponse response) {
SearchHits responseHits = response.getHits();
long total = responseHits.getTotalHits().value;
System.out.println("查询总条数为:"+ total+"........");
SearchHit[] hits = responseHits.getHits();
for (SearchHit hit : hits) {
// 获取的事json格式的字符串
String s = hit.getSourceAsString();
// w我们可以把json转成对象操作
HotelDoc hotelDoc = JSON.parseObject(s, HotelDoc.class);
/**
* 处理高亮结果
*/
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
HighlightField value = highlightFields.get("name");
if (value != null) {
String s1 = value.getFragments()[0].string();
hotelDoc.setName(s1);
}
}
System.out.println(hotelDoc);
}
}
查看日志,就可以发现name属性”如家“ 已经带上高亮的标签了,至于高亮显示那就是前端的事了
黑马旅游案例
启动项目,查看搜搜网页,参数是这几个,key就是条件框输入的参数
酒店搜索和分页
这几个headers、payload 、preview 代表请求路径、参数、返回参数等信息
两个注意点:
1、controller接受的参数,可以用@RequestParam去单个接收,也可以我们后台自定义参数对象去接收用@RequestBody
2、返回条数和酒店数据,返回的对象我们也可以自定义实体类去接收
定义参数对象
@Data
public class RequestParams {
private String key;
private Integer page;
private Integer size;
private String sortBy;
}
定义返回对象,total和list,自定义构造方法,以后返回的时候直接new对象传递参数就可以了,更优雅
@Data
public class PageResult {
private Long total;
private List<HotelDoc> hotels;
public PageResult() {
}
public PageResult(Long total, List<HotelDoc> hotels) {
this.total = total;
this.hotels = hotels;
}
}
定义controller
@RestController
@RequestMapping("/hotel")
@Slf4j
public class HotelController {
@Resource
private IHotelService hotelService;
/**
* 我们这里用对象的接收方式,对象的话,要new一个前端传的实体类参数对象
* 注意对象参数需要加注解@RequestBody,否则是接收不到参数的
* @param requestParams
* @return
*/
@PostMapping("/list")
public PageResult queryHotelListByKey(@RequestBody RequestParams requestParams){
log.info("查询酒店信息,酒店入参_{}", JSON.toJSONString(requestParams));
PageResult pageResult = hotelService.queryHotelListByParams(requestParams);
log.info("获取酒店信息={}", JSON.toJSONString(pageResult));
return pageResult;
}
}
定义service实现
注意:基本类型参与运算不要用包装了,给它拆箱
@Resource
private RestHighLevelClient client;
@Override
public PageResult queryHotelListByParams(RequestParams requestParams) {
// 如果integer类型参与运算的话,就不要用包装类去接收,用它的基本类型接收,给它拆箱
int size = requestParams.getSize();
int from = (requestParams.getPage()-1)*size;
String key = requestParams.getKey();
String sortBy = requestParams.getSortBy();
try {
// 准备DSL语句
SearchRequest request = new SearchRequest("hotel");
// 根据关键字搜索 做好健壮性判断
if (null == key || "".equals(key)) {
// 没有条件就全部查询
request.source().query(QueryBuilders.matchAllQuery());
} else {
request.source().query(QueryBuilders.matchQuery("all", key));
}
// 分页查询
request.source().from(from).size(size);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private PageResult handleResponse(SearchResponse response) {
SearchHits hits = response.getHits();
// 解析条数
long total = hits.getTotalHits().value;
// 解析查询结果 文档数组
SearchHit[] hitsHits = hits.getHits();
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hitsHits) {
String source = hit.getSourceAsString();
// 将原始文档数据转成对象
HotelDoc hotelDoc = JSON.parseObject(source, HotelDoc.class);
// 添加到list集合
hotels.add(hotelDoc);
}
// 封装返回结果,用构造方法去接收,更优雅
return new PageResult(total, hotels);
}
逻辑成功实现了
酒店结果过滤
条件过滤
多条件查询用booleanQuery,原全文检索查询也放到boolQuery里,多个 条件用and都是且的关系
public PageResult queryHotelListByParams(RequestParams requestParams) {
// 如果integer类型参与运算的话,就不要用包装类去接收,用它的基本类型接收,给它拆箱
int size = requestParams.getSize();
int from = (requestParams.getPage()-1)*size;
String key = requestParams.getKey();
String sortBy = requestParams.getSortBy();
String brand = requestParams.getBrand();
String city = requestParams.getCity();
String starName = requestParams.getStarName();
Integer maxPrice = requestParams.getMaxPrice();
Integer minPrice = requestParams.getMinPrice();
try {
SearchRequest request = new SearchRequest("hotel");
// option +command + m 封装一下 boolQuery
BoolQueryBuilder boolQuery = buildQUeryBool(key, brand, city, starName, maxPrice, minPrice);
request.source().query(boolQuery);
// 分页查询
request.source().from(from).size(size);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private BoolQueryBuilder buildQUeryBool(String key, String brand, String city, String starName, Integer maxPrice, Integer minPrice) {
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
// 关键字搜索
// 根据关键字搜索 做好健壮性判断
if (null == key || "".equals(key)) {
// 没有条件就全部查询
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 过滤条件
if (city != null && !"".equals(city)) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
// 品牌条件
if (brand != null && !"".equals(brand)) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
// 星级
if (starName != null && !"".equals(starName)) {
boolQuery.filter(QueryBuilders.termQuery("starName", starName));
}
// 价格
if (maxPrice != null && minPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gt(minPrice).lt(maxPrice));
}
return boolQuery;
}
我周边的酒店
点击地图定位,显示我附近的酒店功能,并且有距离我多远多少km的功能
代码添加完后发现没有距离
代码如下,注意:查询、分页、高亮、排序、都是同级别的,都是source.出来的,不要写到一层去
里边有个sort值,这个值就是距离,这个sort 和我们的source是同级别的,可以用hit点出来,是个数组,将来有可能根据多个字段排序
修改代码,在解析的代码里加上距离的操作就可以了
SearchHits hits = response.getHits();
// 解析条数
long total = hits.getTotalHits().value;
// 解析查询结果 文档数组
SearchHit[] hitsHits = hits.getHits();
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hitsHits) {
String source = hit.getSourceAsString();
// 将原始文档数据转成对象
HotelDoc hotelDoc = JSON.parseObject(source, HotelDoc.class);
// 修改代码,获取sort值 添加到hotelDoc里
Object[] sortValues = hit.getSortValues();
if (sortValues.length>0) {
Object sortValue = sortValues[0];
// 添加距离
hotelDoc.setDistance(sortValue);
}
// 添加到list集合
hotels.add(hotelDoc);
}
// 封装返回结果,用构造方法去接收,更优雅
return new PageResult(total, hotels);
以上按照距离排序,我附近的酒店功能就实现了
酒店竞价排名
就是该算分了,谁掏钱,我让谁制顶,并且有个广告的标识
isAD就广告的缩写AD
索引库文档修改内容添加isAD字段为true 就是打了广告了
POST /hotel/_update/36934
{
"doc" : {
"isAD" : true
}
}
POST /hotel/_update/395702
{
"doc" : {
"isAD" : true
}
}
POST /hotel/_update/395434
{
"doc" : {
"isAD" : true
}
}
算分函数默认是相乘的关系
文章来源:https://www.toymoban.com/news/detail-793829.html
修改代码如下文章来源地址https://www.toymoban.com/news/detail-793829.html
private void buildQueryBool(String key, String brand, String city, String starName, Integer maxPrice, Integer minPrice, RequestParams requestParams, SearchRequest request) {
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
// 关键字搜索
// 根据关键字搜索 做好健壮性判断
if (null == key || "".equals(key)) {
// 没有条件就全部查询
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 过滤条件
if (city != null && !"".equals(city)) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
// 品牌条件
if (brand != null && !"".equals(brand)) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
// 星级
if (starName != null && !"".equals(starName)) {
boolQuery.filter(QueryBuilders.termQuery("starName", starName));
}
// 价格
if (maxPrice != null && minPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gt(minPrice).lt(maxPrice));
}
// 算分控制,以前用的是boolQuery,现在不可以了,boolQuery 是用来做算分的原始查询,现在是用functionScoreQuery
// 两个参数,第一个是接收原始查询(将来影响算分),
// 第二个是接收数组 就是算分函数 new FilterFunctionBuilder[] 数组,因为是内部类,所以new的时候就会用xxx.的方式了
FunctionScoreQueryBuilder functionScoreQueryBuilder =
// 构建FunctionScore
QueryBuilders.functionScoreQuery(
// 原始查询,用来做相关性算分的查询
boolQuery,
// functionScore的一个数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个具体的functionScore元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// boolean 类型也用term 过滤条件
QueryBuilders.termQuery("isAD", true),
// 算分函数
ScoreFunctionBuilders.weightFactorFunction(100)
)
});
// 这个结果将来就要放到source里去了
request.source().query(functionScoreQueryBuilder);
}
到了这里,关于第七章-分布式搜索引擎-ES:全文查询、分词查询、精确查询、地理坐标查询、组合查询(bool、funtion_score)以及RestApi的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!