Elasticsearch提供了基于JSON的DSL来定义查询。常见的查询类型包括:
查询所有:查询到所有的数据,一般测试用:
match_all
全文检索:(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。
match_query
multi_match_query
精确查询:根据精确词条值查找数据,一般是查找keyword,数值,日期,boolean等类型字段。
ids
range
term
地理查询:根据经纬度查询
geo_distance
geo_bounding_box
复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件
bool
function_score
一般测试用
#查询所有
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
会对用户输入内容分词,常用于搜索框搜索
#全文检索查询,会对用户输入的内容分词,常用于搜索框搜索(依据相关度排序)
#match:根据一个字段查询
GET /hotel/_search
{
"query": {
"match": {
"all": "外滩如家"
}
}
}
#multi_match:与match查询类似,只不过允许同时查询多个字段。根据多个字段查询,
参与查询字段越多,性能越差(建议cope_to)
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand","name","business"]
}
}
}
一般是查找keyword,数值,日期,boolean等类型字段。所以不会对搜索条件分词
#精确查询一般是查找keyword,数值,日期,boolean等类型字段。所以不会对搜索条件分词,常见有:
#term:根据词条精确值查询
GET /hotel/_search
{
"query": {
"term": {
"brand": {
"value": "如家"
}
}
}
}
#range:根据值的范围查询
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
}
常用于搜索自己附近的地点或车酒店等
#根据经纬度查询
#geo_bounding_box查询geo_point值落在某个矩形范围的所有文档
GET /hotel/_search
{
"query": {
"geo_bounding_box":{
"location":{
"top_left":{
"lat":31,
"lon":121.5
},
"bottom_right":{
"lat":30.9,
"lon":121.7
}
}
}
}
}
#geo_distance:查询到指定中心点小于某个距离值的所有文档
GET /hotel/_search
{
"query": {
"geo_distance":{
"distance":"2km",
"location":"31.21,121.5"
}
}
}
将其他简单的查询组合起来,实现更加复杂的搜索逻辑。
注意:利用match查询时,文档会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。
#复合查询:复合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑。
#function score:算分函数排序,可以控制文档相关性算分,控制文档排名
GET /hotel/_search
{
"query": {
"function_score": {
"query": {"match": {
"all": "外滩"
}},
"functions": [
{
"filter": {"term": {
"id": "1"
}},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
#例子:给如家这个品牌的酒店排名靠前一些
GET /hotel/_search
{
"query": {
"function_score": {
"query": {"match": {
"all": "外滩"
}},
"functions": [
{
"filter": {"term": {
"brand": "如家"
}},
"weight": 10
}
],
"boost_mode": "sum"
}
}
}
布尔查询是一个或多个查询子句的组合
must:必须匹配每个子查询(与)
should:选择性匹配子查询(或)
must_not:必须不匹配,不参与算分(非)
filter:必须匹配,不参与算分
#Boolean Query
#布尔查询是一个或多个查询子句的组合,子查询的组合方式有:
#must:必须匹配每个子查询,“与”
#should:选择性匹配子查询,“或”
#must_not:必须不匹配,不参与算分,“非”
#filter:必须匹配,不参与算分
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{"term": {
"city": {
"value": "上海"
}
}}
],
"should": [
{"term": {
"brand": {
"value": "皇冠假日"
}
}},
{"term": {
"brand": {
"value": "华美达"
}
}}
],
"must_not": [
{"range": {
"price": {
"lte": 500
}
}}
],
"filter": [
{"range": {
"score": {
"gte": 45
}
}}
]
}
}
}
#利用bool查询实现功能
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "如家"
}}
],
"must_not": [
{"range": {
"price": {
"gte": 400
}
+ "lat": 31.21,
"lon": 121.5
}
}}
]
}
}
}
elasticseach支持对搜索结果排序,默认是根据相度算分来排序。可以排序字段类型有keyword类型,数值类型,地理坐标类型,日期类型等。
GET /hotel/_search
{
"query": {"match_all": {}},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
GET /hotel/_search
{
"query": {"match_all": {}},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 40,
"lon": -70
},
"order": "desc",
"unit": "km"
}
}
]
}
GET /hotel/_search
{
"query": {"match_all": {}},
"sort": [
{
"score":"desc"
},
{
"price":"asc"
}
]
}
#找到121.612282,31.034661周围的酒店,距离升序排序
GET /hotel/_search
{
"query": {"match_all": {}},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.034661,
"lon": 121.612282
},
"order": "asc",
"unit": "km"
}
}
]
}
elasticsearch默认情况下只返回top10的数据,而如果要查询更多数据就需要修改分页参数了。
elasticsearch中通过修改from,size参数来控制要返回的分页结果
#elasticsearch默认情况下只返回top 10的数据,而如果要查询更多的数据就需要修改页面参数了。
#from:默认从0开始(分页开始的位置)
#size:几条数据(期望获取的文档总数)
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 991,
"size": 11,
"sort": [
{
"price": {
"order": "asc"
}
}
]
}
深度分页问题:ES是分布式的,所以会面临深度分页问题,ES设定结果集查询的上限是10000。
解决方案:
1.search after:分页时需要排序,原理上是从上一次的排序值开始,查询下一页数据
2.score:原理将排序数据形成快照,保存在内存中,官方已经不推荐使用
在搜索结果中把搜索关键字突出显示。
原理:
将搜索结果中的关键字用标签标记出来
在页面中给标签加css样式
#高亮:就是在搜索结果中把搜索关键字突出显示
#默认情况下,ES搜索字段必须与高亮字段一致,否则无法高亮
#require_field_match:需不需要字段匹配
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name":{
"pre_tags":"",
"post_tags":"",
"require_field_match": "false"
}
}
}
}
GET /hotel/_search
{
"query": {
"match": {
"name": "如家"
}
},
"from": 0, // 分页开始的位置
"size": 20, // 期望获取的文档总数
"sort": [
{ "price": "asc" }, // 普通排序
{
"_geo_distance" : { // 距离排序
"location" : "31.040699,121.618075",
"order" : "asc",
"unit" : "km"
}
}
],
"highlight": {
"fields": { // 高亮字段
"name": {
"pre_tags": "", // 用来标记高亮字段的前置标签
"post_tags": "" // 用来标记高亮字段的后置标签
}
}
}
}
解析结果有些重复,所以提前抽取成方法
public class QueryTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client=new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.122.128:9200")
));
}
@AfterEach
void tearDown() throws IOException {
client.close();
}
/*
* 结果集处理方法
* */
private void ResponseHandle(SearchResponse response) {
//4.解析响应结果
SearchHits searchHits = response.getHits();
//4.1.查询的总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
//4.2.查询的结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
//4.4.反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
/*
* 高亮解析
* */
Map highlightFields = hit.getHighlightFields();
if (highlightFields!=null || highlightFields.size()!=0){
//2.获取高亮字段结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField!=null){
//取出高亮结果数组中的第一个,就是酒店名称
String name=highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
}
//4.5.打印
System.out.println(hotelDoc);
}
}
}
/*
* 结果集处理方法
* */
private PageResult ResponseHandle(SearchResponse response) {
//4.解析响应结果
SearchHits searchHits = response.getHits();
//4.1.查询的总条数
long total = searchHits.getTotalHits().value;
// System.out.println("共搜索到" + total + "条数据");
//4.2.查询的结果数组
SearchHit[] hits = searchHits.getHits();
//遍历获取结果
ArrayList hotels = new ArrayList<>();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
//4.4.反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取排序值
Object[] sortValues = hit.getSortValues();
if (sortValues.length>0){
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue);
}
hotels.add(hotelDoc);
// /*
// * 高亮解析
// * */
// Map highlightFields = hit.getHighlightFields();
// if (highlightFields!=null || highlightFields.size()!=0){
// //2.获取高亮字段结果
// HighlightField highlightField = highlightFields.get("name");
// if (highlightField!=null){
// //取出高亮结果数组中的第一个,就是酒店名称
// String name=highlightField.getFragments()[0].string();
// hotelDoc.setName(name);
// }
// }
// //4.5.打印
// System.out.println(hotelDoc);
}
//封装返回
return new PageResult(total,hotels);
}
/*
* 查询所有
* */
@Test
void matchAllTest() throws IOException {
//1.准备request
SearchRequest request = new SearchRequest("hotel");
//2.组织DSL参数
request.source().query(QueryBuilders.matchAllQuery());
//3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ResponseHandle(response);
}
/*
* 全文检索
* */
@Test
void matchTest() throws IOException {
SearchRequest request = new SearchRequest("hotel");
// request.source().query(QueryBuilders.matchQuery("all","外滩如家"));
request.source().query(QueryBuilders.multiMatchQuery("如家","name","business"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ResponseHandle(response);
}
/*
* 精确查询
* */
@Test
void TrueTest() throws IOException {
//1.准备request
SearchRequest request = new SearchRequest("hotel");
//2.组织DSL参数
// request.source().query(QueryBuilders.termQuery("brand","如家"));
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(200));
//3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ResponseHandle(response);
}
/*
* 组合查询
* */
@Test
void boolTest() throws IOException {
//1.准备request
SearchRequest request = new SearchRequest("hotel");
// //创建布尔查询
// BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// //添加must条件
// boolQuery.must(QueryBuilders.termQuery("city","杭州"));
// //添加filter条件
// boolQuery.filter(QueryBuilders.rangeQuery("price").lte(500));
// request.source().query(boolQuery);
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("city", "深圳"))
.filter(QueryBuilders.rangeQuery("price").gte(250))
);
//3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ResponseHandle(response);
}
/*
* 查询所有的分页查询和排序以及高亮
* */
@Test
void pageTest() throws IOException {
int page=1;
int pageSize=5;
//1.准备request
SearchRequest request = new SearchRequest("hotel");
//2.组织DSL参数
/*
* 高亮需对关键字高亮,必须有条件,不能用matchAll了
* */
request.source().query(QueryBuilders.matchQuery("all","如家"));
//分页查询
request.source().from((page-1)*pageSize).size(3);
//排序
request.source().sort("price", SortOrder.ASC);
//高亮
request.source().highlighter(
new HighlightBuilder()
.field("name")
//是否需要和查询字段匹配
.requireFieldMatch(false)
);
/*
* 高亮结果解析比较麻烦
* */
//3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
ResponseHandle(response);
}
总结:查询的基本步骤是:
创建SearchRequest对象
准备Request.source(),也就是DSL
QueryBuilders来构建查询条件
出入Request.source()的query方法
发送请求,得到结果
解析结果()