查询方法
查询所有:查询出所有数据,一般测试用。例如:match_all
GET _search
{
"query": {
"match_all": {}
}
}
request.source().query(
QueryBuilders.matchAllQuery()
);
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
match查询:单字段查询
multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件
- 搜索字段越多,对查询性能影响越大,因此建议采用copy_to,然后单字段查询的方式
#全文检索 GET /hotel/_search { "query": { "match": { "all": "如家" } } } GET /hotel/_search { "query": { "multi_match": { "query": "7天", "fields": ["name","brand","city"] } } }
request.source().query( QueryBuilders.matchQuery("all", "如家") ); request.source().query( QueryBuilders.multiMatchQuery("如家", "name", "brand", "city") );
精确查询:
- 精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有
ids
term:根据词条精确值查询
range:范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。
因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据。
#精确查询 #term 根据词条精确查询 #range 根据值的范围查询 GET /hotel/_search { "query": { "term": { "city": { "value": "北京" } } } } GET /hotel/_search { "query": { "range": { "price": { "gte": 100, "lte": 1000 } } } }
request.source().query( QueryBuilders.termQuery("city", "北京") ); request.source().query( QueryBuilders.rangeQuery("price") .gt(1000) .lt(3000) );
地理(geo)查询:根据经纬度查询。例如:
geo_distance
geo_bounding_box
#地理查询 #查询附件的坐标点 GET /hotel/_search { "query": { "geo_distance":{ "distance" : "15km", "location" : "31.21,121.5" } } }
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
GET /hotel/_search { "query": { "bool": { "must": [ { "match": { "name": "如家" } } ], "must_not": [ { "range": { "price": { "gt": 400 } } } ], "filter": [ { "geo_distance": { "distance": "15km" , "location": { "lat": 31.21, "lon": 121.5 } } } ] } } }
算分函数
GET /hotel/_search { "query": { "function_score": { "query": { "match": { "all": "外滩" } }, "functions": [ { "filter": { "term": { "brand": "如家" } } , "weight": 10 } ], "boost_mode": "sum" } } }
值得注意的是,算分函数,在查询语句要在算分函数内,
意义是先查到文档,然后过滤,进行算分
must:必须匹配每个子查询,类似“与”
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分,类似“非”
filter:必须匹配,不参与算分
- 值得注意的是,如果进行排序了,原始的算分排序就失效了
- 普通排序
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "FIELD": "desc" // 排序字段、排序方式ASC、DESC } ] }
地理排序
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance" : { "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点 "order" : "asc", // 排序方式 "unit" : "km" // 排序的距离单位 } } ] }
分页
GET /hotel/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ {"price": "asc"} ] }
深度分页问题
现在,我要查询990~1000的数据,查询逻辑要这么写
高亮原理
- 值得注意的是高亮的时候,必须有搜索值的传入
#高亮 #就是在搜索结果中把所有关键字突出显示 GET /hotel/_search { "query": { "match": { "all": "如家" } }, "highlight": { "fields": { "name": { "require_field_match": "false" } } } }
下面是统一的java,api实现
@Service public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService { @Resource private RestHighLevelClient client; @Override public PageResult search(RequestParams params) { //查询数据 SearchRequest request = new SearchRequest("hotel"); //使用bool复合查询 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); //判断params有没有key String key = params.getKey(); if (StringUtils.isBlank(key)){ //查所有 boolQueryBuilder.must(QueryBuilders.matchAllQuery()); }else { boolQueryBuilder.must(QueryBuilders.matchQuery("all",key)); //有搜索项就可以高亮啦 request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false) ); } if (StringUtils.isNotBlank(params.getBrand())){ //如果又品牌条件使用不参与算分 boolQueryBuilder.filter(QueryBuilders.termQuery("brand",params.getBrand())); } if (StringUtils.isNotBlank(params.getCity())){ //如果又城市条件使用不参与算分 boolQueryBuilder.filter(QueryBuilders.termQuery("city",params.getCity())); } if (StringUtils.isNotBlank(params.getStarName())){ //如果又星级条件使用不参与算分 boolQueryBuilder.filter(QueryBuilders.termQuery("starName",params.getStarName())); } if (params.getMinPrice()!=null&¶ms.getMaxPrice()!=null){ //如果价钱又限制,不参与算分 boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice())); } //加广告 FunctionScoreQueryBuilder isAD = QueryBuilders.functionScoreQuery( boolQueryBuilder, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true), ScoreFunctionBuilders.weightFactorFunction(10)) } ).boostMode(CombineFunction.MULTIPLY); request.source().query(isAD); //如果又距离的话需要根据距离排序 if (StringUtils.isNotBlank(params.getLocation())){ request.source().sort(SortBuilders.geoDistanceSort( "location",new GeoPoint(params.getLocation()) ).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC)); } //分页 Integer size = params.getSize(); int page = (params.getPage() - 1) * size; request.source().from(page).size(size); //显示距离 try { SearchResponse response = client.search(request, RequestOptions.DEFAULT); PageResult pageResult = new PageResult(); SearchHits searchHits = response.getHits(); long value = searchHits.getTotalHits().value; SearchHit[] hits = searchHits.getHits(); pageResult.setTotal(value); List<HotelDoc> hotelDocs = new ArrayList<>(); for (SearchHit hit : hits) { String json = hit.getSourceAsString(); HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class); //需要获得地址距离,和高亮的覆盖 Object[] sortValues = hit.getSortValues(); if (sortValues.length>0){ hotelDoc.setDistance(sortValues[0]); } //显示高亮 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); if (CollectionUtils.isNotEmpty(highlightFields)){ HighlightField name = highlightFields.get("name"); if (Objects.nonNull(name)){ String s = name.getFragments()[0].toString(); hotelDoc.setName(s); } } hotelDocs.add(hotelDoc); } pageResult.setHotels(hotelDocs); return pageResult; } catch (IOException e) { throw new RuntimeException(e); } //解析返回值 } }