Elasticsearch提供了DSL ( Domain Specific Language)查询,就是以SON格式来定义查询条件
DSL查询可以分为两大类:
叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
复合查询(Compound query clauses)︰以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。
在查询以后,还可以对查询的结果做处理,包括:
- 排序:按照1个或多个字段值做排序
- 分页:根据from和size做分页,类似MySQL
- 高亮:对搜索结果中的关键字添加特殊样式,使其更加醒目·聚合:对搜索结果做数据统计以形成报表
快速入门
# 查询所有
GET /heima/_search
{
"query": {
"match_all": {}
}
}
叶子查询
叶子查询还可以进一步细分,常见的有:
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去词条列表中匹配。例如:
- match_query
- multi_match_query
精确查询:不对用户输入内容分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。例如:
- ids
- range
- term
地理(geo)查询:用于搜索地理位置,搜索方式很多。例如:
- geo_distance
- geo_bounding_box
match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:
# mach查询
GET /heima/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
}
}
multi_match: 与match查询类似,只不过允许同时查询多个字段,语法:
精确查询
精确查询,英文是Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。
因此推荐查找keyword、数值、日期、boolean类型的字段。例如id、price、城市、地名、人名等作为一个整体才有含义的字段。
# term查询
GET /items/_search
{
"query": {
"term": {
"brand": {
"value": "德亚"
}
}
}
}
# range查询
GET /items/_search
{
"query": {
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
}
# ids查询
GET /items/_search
{
"query": {
"ids": {
"values": ["613359"]
}
}
}
复合查询
复合查询大致可以分为两类:
第一类:基于逻辑运算组合叶子查询,实现组合条件,例如
- bool
第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:
- function_score
- dis_max
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
排序和分页
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序,也可以指定字段排序。可以排序字段类型有: keyword类型、数值类型、地理坐标类型、日期类型等。
排序
# 排序查询
GET /items/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"sold": "desc"
},
{
"price": "asc"
}
]
}
分页
elasticsearch默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
# 排序及分页查询
GET /items/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"sold": "desc"
},
{
"price": "asc"
}
],
"from": 0,
"size": 10
}
深度分页问题
elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。查询数据时需要汇总各个分片的数据。
假如我们要查询第100页数据,每页查询10条:
那么实现的思路:首先对数据进行排序,然后再去找出990~1000名的数据
在底层是把每一片里的前1000个数据选出来,然后聚合重新排序选取前1000个,当我们的数据非常多并且页数非常靠后时,就会导致效率非常差
解决办法:
search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的式。
scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
优点:没有查询上限,支持深度分页
缺点:只能向后逐页查询,不能随机翻页
场景:数据迁移,手机滚动查询
高亮显示
就是在搜索结果中把搜索关键字突出显示
#高量
GET /items/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
}
}
}
搜索的完整语法
JavaRestClient查询
快速入门
数据搜索的Java代码我们分为两部分:
- 构建并发起请求
- 解析查询结果
@Test
void testMachAll() throws IOException {
//1.配置request对象
SearchRequest request = new SearchRequest("items");
//2.配置request参数
request.source()
.query(QueryBuilders.matchAllQuery());
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
SearchHits searchHits = response.getHits();
//4.1总条数
long total = searchHits.getTotalHits().value;
//4.2命中的数据
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//获取source结果
String json = hit.getSourceAsString();
//转成对象
ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
System.out.println("doc = "+doc);
}
}
构建查询条件
在JavaRestAPI中,所有类型的query查询条件都是由QueryBuilders来构建的:
@Test
void testSearch() throws IOException {
//1.配置request对象
SearchRequest request = new SearchRequest("items");
//2.配置request参数
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name","脱脂牛奶"))
.filter(QueryBuilders.termQuery("brand","德亚"))
.filter(QueryBuilders.rangeQuery("price").lt(30000))
);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
parseResponseResult(response);
}
排序和分页
@Test
void testSortAndPage() throws IOException {
//模拟前端传来的数据
int pageNo = 1, pageSize =5;
//1.配置request对象
SearchRequest request = new SearchRequest("items");
//2.配置request参数
request.source().query(QueryBuilders.matchAllQuery());
//分页
request.source().from((pageNo-1)*pageSize).size(pageSize);
//排序
request.source()
.sort("sold", SortOrder.DESC)
.sort("price",SortOrder.ASC);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
parseResponseResult(response);
}
高亮显示
@Test
void testHighlight() throws IOException {
//1.配置request对象
SearchRequest request = new SearchRequest("items");
//2.配置request参数
request.source().query(QueryBuilders.matchQuery("name","脱脂牛奶"));
//高量条件
request.source().highlighter(SearchSourceBuilder.highlight().field("name"));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
parseResponseResult(response);
}
private static void parseResponseResult(SearchResponse response){
SearchHits searchHits = response.getHits();
//4.1总条数
long total = searchHits.getTotalHits().value;
//4.2命中的数据
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//获取source结果
String json = hit.getSourceAsString();
//转成对象
ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
//处理高亮结果
Map<String, HighlightField> hfs = hit.getHighlightFields();
if(hfs!=null && !hfs.isEmpty()){
//根据高亮字段名获取高亮结果
HighlightField hf = hfs.get("name");
//获取高亮结果,覆盖非高亮结果
String hfName = hf.getFragments()[0].string();
doc.setName(hfName);
}
System.out.println("doc = "+doc);
}
}
数据聚合
聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:
桶( Bucket)聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max. min.ava、sum等
管道( pipeline)聚合:其它聚合的结果为基础做聚合
注意:参与聚合的字段必须是Keyword、数值、日期、布尔的类型的字段
DSL聚合
我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合。
GET /items/_search
{
"size": 0,
"aggs": {
"category_agg": {
"terms": {
"field": "category",
"size": 20
}
}
}
}
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可。例如,我想知道价格高于3000元的手机品牌有哪些:
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}
除了对数据分组(Bucket)以外,我们还可以对每个Bucket内的数据进一步做数据计算和统计。例如:我想知道手机有哪些品牌,每个品牌的价格最小值、最大值、平均值。
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": {
"stats_meric": {
"stats": {
"field": "price"
}
}
}
}
}
}
RestClient聚合
@Test
void testAgg() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.准备请求参数
BoolQueryBuilder bool = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("category", "手机"))
.filter(QueryBuilders.rangeQuery("price").gte(300000));
request.source().query(bool).size(0);
// 3.聚合参数
request.source().aggregation(
AggregationBuilders.terms("brand_agg").field("brand").size(5)
);
// 4.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 5.解析聚合结果
Aggregations aggregations = response.getAggregations();
// 5.1.获取品牌聚合
Terms brandTerms = aggregations.get("brand_agg");
// 5.2.获取聚合中的桶
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 5.3.遍历桶内数据
for (Terms.Bucket bucket : buckets) {
// 5.4.获取桶内key
String brand = bucket.getKeyAsString();
System.out.print("brand = " + brand);
long count = bucket.getDocCount();
System.out.println("; count = " + count);
}
}