1.查询的分类
- 查询所有:查询出所有数据,一般测试用。例如:match_all
- 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
- 地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
查询的语法基本一致:
GET /indexName/_search{"query": {"查询类型": {"查询条件": "条件值"}}}
2.全文检索查询
- match查询:单字段查询
- multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件
3.精准查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。
3.1term查询
// term查询GET /indexName/_search{"query": {"term": {"FIELD": {"value": "VALUE"}}}}
3.2range查询 范围查询
// range查询GET /indexName/_search{"query": {"range": {"FIELD": {"gte": 10, // 这里的gte代表大于等于,gt则代表大于"lte": 20 // lte代表小于等于,lt则代表小于}}}}
4.地理坐标查询
地理坐标查询,其实就是根据经纬度查询
4.1矩形范围查询
GET hotel/_search{"query":{"geo_bounding_box":{"location":{"top_left": {"lat": 31.1,"lon": 121.5},"bottom_right":{"lat": 30.9,"lon": 121.7}}}}}
4.2附近查询
// geo_distance 查询GET /indexName/_search{"query": {"geo_distance": {"distance": "15km", // 半径"FIELD": "31.21,121.5" // 圆心}}}
5.复合查询
5.1算分函数查询
function score 查询中包含四部分内容:
- 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
- 过滤条件:filter部分,符合该条件的文档才会重新算分
- 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
- weight:函数结果是常量
- field_value_factor:以文档中的某个字段值作为函数结果
- random_score:以随机数作为函数结果
- script_score:自定义算分函数算法
- 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
- multiply:相乘
- replace:用function score替换query score
- 其它,例如:sum、avg、max、min
function score的运行流程如下:
- 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
- 2)根据过滤条件,过滤文档
- 3)符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
- 4)将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。
function score query定义的三要素是什么?
- 过滤条件:哪些文档要加分
- 算分函数:如何计算function score
- 加权方式:function score 与 query score如何运算
5.2布尔查询
布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
需要注意的是,搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:
- 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
- 其它过滤条件,采用filter查询。不参与算分
6.搜索结果处理
6.1排序
普通字段排序
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" // 排序的距离单位}}]}
6.2分页
GET /hotel/_search{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10, // 期望获取的文档总数"sort": [{"price": "asc"}]}
6.3高亮
GET /hotel/_search{"query": {"match": {"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询}},"highlight": {"fields": { // 指定要高亮的字段"FIELD": {"pre_tags": "<em>", // 用来标记高亮字段的前置标签"post_tags": "</em>" // 用来标记高亮字段的后置标签}}}}
注意:
- 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
- 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
7.RestClient查询文档
文档的查询同样适用昨天学习的 RestHighLevelClient对象,基本步骤包括:
- 准备Request对象
- 准备请求参数
- 发起请求
- 解析响应
public class HotelSearchTest {
private RestHighLevelClient client;
//查询全部
@Test
void testMatchAll() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().query(QueryBuilders.matchAllQuery());
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//System.out.println(response);
//4.解析响应
handleResponse(response);
}
//查询指定部分
@Test
void testMatch() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().query(QueryBuilders.matchQuery("all","如家"));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//System.out.println(response);
handleResponse(response);
}
//布尔条件查询
@Test
void testBool() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1准备BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//2.2添加term
boolQuery.must(QueryBuilders.termQuery("city","杭州"));
//2.3添加range
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//System.out.println(response);
handleResponse(response);
}
//分页
@Test
void testPageAndSort() throws IOException {
//页码 每页大小
int page = 2, size = 5;
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 query
request.source().query(QueryBuilders.matchAllQuery());
//2.2 排序sort
request.source().sort("price", SortOrder.ASC);
//2.3 分页from size
request.source().from((page - 1) * size).size(5);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//System.out.println(response);
handleResponse(response);
}
//高亮
@Test
void testHighlight() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 query
request.source().query(QueryBuilders.matchQuery("all","如家"));
//2.2 高亮
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//System.out.println(response);
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
//4.解析响应
SearchHits searchHits = response.getHits();
//4.1获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
//4.2文档数组
SearchHit[] hits = searchHits.getHits();
//4.3遍历
for (SearchHit hit : hits) {
//获取文档source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
//根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
//获取高亮值
String name = highlightField.getFragments()[0].string();
//覆盖非高亮结果
hotelDoc.setName(name);
}
}
System.out.println("hotelDoc" + hotelDoc);
}
}
@BeforeEach
void setUp(){
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.18.99:9200")
));
}
@AfterEach
void tearDowm() throws IOException {
this.client.close();
}
}
