DSL查詢語法
DSL Query的分類
Elasticsearch提供了基于JSON的DSL (Domain Specific Language)來定義查詢。常見的查詢類型包括:
- 查詢所有:查詢出所有數(shù)據(jù),一般測試用。例如:match_all
- 全文檢索(full text)查詢:利用分詞器對用戶輸入內(nèi)容分詞,然后去倒排索引庫中匹配。例如:
- match_query
- multi_match_query
- 精確查詢:根據(jù)精確詞條值查找數(shù)據(jù),一般是查找keyword、數(shù)值、日期、boolean等類型字段。例如:
- ids
- range
- term
- 地理(geo)查詢:根據(jù)經(jīng)緯度查詢。例如︰
- geo_distance
- geo_bounding_box
- 復(fù)合(compound)查詢:復(fù)合查詢可以將上述各種查詢條件組合起來,合并查詢條件。例如
- bool
- function_score
DSL Query基本語法
查詢的基本語法如下:
GET /indexName/_search
{
"query": {
"查詢類型": {
"查詢條件": "條件值"
}
}
}
//查詢所有
GET /indexName/_search{
"query": {
"match_all": {
}
}
}
全文檢索查詢
全文檢索查詢,會(huì)對用戶輸入內(nèi)容分詞,常用于搜索框搜索
match查詢:全文檢索查詢的一種,會(huì)對用戶輸入內(nèi)容分詞,然后去倒排索引庫檢索,語法:
GET /indexName/_search{
"query" : {
"match" : {
"FIELD": "TEXT"
}
}
}
例如:
GET /hotel/_search
{
"query": {
"match": {
"all": "外灘如家"
}
}
}
multi_match: 與match查詢類似,只不過允許同時(shí)查詢多個(gè)字段,語法:
GET /indexName/_search{
"query": {
"multi_match" : {
"query" : "TEXT",
"fields" :["FIELD1","FIELD12"]
}
}
}
例如:
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外灘如家",
"fields": ["brand","business","name"]
}
}
}
match和multi_match的區(qū)別是什么?
- match:根據(jù)一個(gè)字段查詢
- multi_match:根據(jù)多個(gè)字段查詢,參與查詢字段越多,查詢性能越差
精確查詢
精確查詢一般是查找keyword、數(shù)值、日期、boolean等類型字段。所以不會(huì)對搜索條件分詞。常見的有:
- term:根據(jù)詞條精確值查詢
- range:根據(jù)值的范圍查詢
精確查詢-語法
精確查詢一般是根據(jù)id、數(shù)值、keyword類型、或布爾字段來查詢。語法如下:
-
term查詢:
// term查詢 GET /indexName/_search{ "query": { "term" : { "FIELD":{ "value" : "VALUE" } } } }
例如:
GET /hotel/_search { "query": { "term": { "city": { "value": "上海" } } } }
-
range查詢:
// range查詢 GET /indexName/_search{ "query" : { "range" : { "FIELD": { "gte": 10, "lte": 20 } } } }
例如:
GET /hotel/_search { "query": { "range": { "price": { "gte": 100, "lte": 300 } } } }
gt:大于
gte:大于等于
地理查詢
根據(jù)經(jīng)緯度查詢。常見的使用場景包括:
- 攜程:搜索我附近的酒店
- 滴滴:搜索我附近的出租車
- 微信:搜索我附近的人
根據(jù)經(jīng)緯度查詢,官方文檔。例如:
-
geo_bounding_box:查詢geo_point值落在某個(gè)矩形范圍的所有文檔
//geo_bounding_box查詢 GET /indexName/_search{ "query" : { "geo_bounding_box" : { "FIELD":{ "top_left" : { "lat" : 31.1, "lon": 121.5 }, "bottom_right":{ "lat": 30.9, "lon": 121.7 } } } } }
-
geo_distance:查詢到指定中心點(diǎn)小于某個(gè)距離值的所有文檔
//geo_distance查詢 GET /indexName/_search{ "query" : { "geo_distance": { "distance": "15km", "FIELD": "31.21,121.5" } } }
例如:
GET /hotel/_search { "query": { "geo_distance": { "distance": "5km", "location": "31.21,121.5" } } }
復(fù)合查詢
復(fù)合(compound)查詢:復(fù)合查詢可以將其它簡單查詢組合起來,實(shí)現(xiàn)更復(fù)雜的搜索邏輯,例如:
- fuction score:算分函數(shù)查詢,可以控制文檔相關(guān)性算分,控制文檔排名。例如百度競價(jià)
相關(guān)性算分
當(dāng)我們利用match查詢時(shí),文檔結(jié)果會(huì)根據(jù)與搜索詞條的關(guān)聯(lián)度打分(_score),返回結(jié)果時(shí)按照分值降序排列。例如,我們搜索"虹橋如家",結(jié)果如下:
[
{
"_score" : 17.850193,
"_source": {
"name" :"虹橋如家酒店真不錯(cuò)",
}
},
{
"_score" : 12.259849,
"_source" : {
"name" : "外灘如家酒店真不錯(cuò)",
}
},
{
"_score" :11.91091,
"_source" : {
"name" :"迪士尼如家酒店真不錯(cuò)",
}
}
]
T F ( 詞條頻率 ) = 詞條出現(xiàn)次數(shù) 文檔中詞條總數(shù) TF(詞條頻率)=\frac{詞條出現(xiàn)次數(shù)}{文檔中詞條總數(shù)} TF(詞條頻率)=文檔中詞條總數(shù)詞條出現(xiàn)次數(shù)?
T F ? I D F 算法 I D F ( 逆文檔頻率 ) = l o g ( 文檔總數(shù) 句含詞條的文檔總數(shù) ) s c o r e = ∑ i n T F ( 詞條頻率 ) ? I D F ( 逆文檔頻率 ) TF-IDF算法 \\ IDF(逆文檔頻率)= log(\frac{文檔總數(shù)}{句含詞條的文檔總數(shù)})\\ score =\sum_{i}^{n}{TF(詞條頻率)*IDF(逆文檔頻率)} TF?IDF算法IDF(逆文檔頻率)=log(句含詞條的文檔總數(shù)文檔總數(shù)?)score=i∑n?TF(詞條頻率)?IDF(逆文檔頻率)
B M 25 算法 S c o r e ( Q , d ) = ∑ i n l o g ( 1 + N ? n + 0.5 n + 0.5 ) ? f i f i + k 1 ? ( 1 ? b + b ? d l a v g d l ) BM25算法\\ Score(Q,d) = \sum_i^n{log(1+\frac{N-n+0.5}{n+0.5})}*\frac{f_i}{f_i+k_1*(1-b+b*\frac{dl}{avgdl})} BM25算法Score(Q,d)=i∑n?log(1+n+0.5N?n+0.5?)?fi?+k1??(1?b+b?avgdldl?)fi??
elasticsearch中的相關(guān)性打分算法是什么?
- TF-IDF:在elasticsearch5.0之前,會(huì)隨著詞頻增加而越來越大
- BM25:在elasticsearch5.0之后,會(huì)隨著詞頻增加而增大,但增長曲線會(huì)趨于水平
Function Score Query
使用function score query,可以修改文檔的相關(guān)性算分(query score),根據(jù)新得到的算分排序。
復(fù)合查詢Boolean Query
布爾查詢是一個(gè)或多個(gè)查詢子句的組合。子查詢的組合方式有:
- must:必須匹配每個(gè)子查詢,類似“與”
- should:選擇性匹配子查詢,類似“或”
- must_not:必須不匹配,不參與算分,類似“非”
- filter:必須匹配,不參與算分
bool查詢有幾種邏輯關(guān)系?
- must:必須匹配的條件,可以理解為“與”
- should:選擇性匹配的條件,可以理解為“或”
- must_not:必須不匹配的條件,不參與打分
- filter:必須匹配的條件,不參與打分
搜索結(jié)果處理
排序
elasticsearch支持對搜索結(jié)果排序,默認(rèn)是根據(jù)相關(guān)度算分(_score)來排序。可以排序字段類型有: keyword類型、數(shù)值類型、地理坐標(biāo)類型、日期類型等。
GET /indexName /_search
{
"query " : {
"match_all":{}
},
"sort":[
{
"FIELD": "desc" //排序字段和排序方式ASC、DESC
}
]
}
GET /indexName /_search
{
"query " : {
"match_all":{}
},
"sort":[
{
"_geo_distance" : {
"FIELD" :"緯度,經(jīng)度",
"order" : "asc",
"unit" : "km"
}
}
]
}
分頁
elasticsearch默認(rèn)情況下只返回top10的數(shù)據(jù)。而如果要查詢更多數(shù)據(jù)就需要修改分頁參數(shù)了。elasticsearch中通過修改from、size參數(shù)來控制要返回的分頁結(jié)果:
GET /hotel/_search{
"query": {
"match_all": {}
},
"from": 990,//分頁開始的位置,默認(rèn)為0
"size": 10,//期望獲取的文檔總數(shù)
"sort":[
{"price": "asc"}
]
}
es的數(shù)據(jù)結(jié)構(gòu)使得它的分頁查詢不是真正的的分頁
深度分頁問題
ES是分布式的,所以會(huì)面臨深度分頁問題。例如按price排序后,獲取from = 990,size =10的數(shù)據(jù):
- 首先在每個(gè)數(shù)據(jù)分片上都排序并查詢前1000條文檔。
- 然后將所有節(jié)點(diǎn)的結(jié)果聚合,在內(nèi)存中重新排序選出前1000條文檔
- 最后從這1000條中,選取從990開始的10條文檔
如果搜索頁數(shù)過深,或者結(jié)果集(from + size)越大,對內(nèi)存和CPU的消耗也越高。因此ES設(shè)定結(jié)果集查詢的上限是10000
深度分頁解決方案
針對深度分頁,ES提供了兩種解決方案,官方文檔:
- search after:分頁時(shí)需要排序,原理是從上一次的排序值開始,查詢下一頁數(shù)據(jù)。官方推薦使用的方式。
- scroll:原理將排序數(shù)據(jù)形成快照,保存在內(nèi)存。官方已經(jīng)不推薦使用。
from + size:
-
優(yōu)點(diǎn):支持隨機(jī)翻頁
-
缺點(diǎn):深度分頁問題,默認(rèn)查詢上限( from + size)是10000
-
場景:百度、京東、谷歌、淘寶這樣的隨機(jī)翻頁搜索
after search:
- 優(yōu)點(diǎn):沒有查詢上限(單次查詢的size不超過10000)
- 缺點(diǎn):只能向后逐頁查詢,不支持隨機(jī)翻頁
- 場景:沒有隨機(jī)翻頁需求的搜索,例如手機(jī)向下滾動(dòng)翻頁
scroll:
- 優(yōu)點(diǎn):沒有查詢上限(單次查詢的size不超過10000)
- 缺點(diǎn):會(huì)有額外內(nèi)存消耗,并且搜索結(jié)果是非實(shí)時(shí)的
- 場景:海量數(shù)據(jù)的獲取和遷移。從ES7.1開始不推薦,建議用after search方案。
高亮
高亮:就是在搜索結(jié)果中把搜索關(guān)鍵字突出顯示。
原理是這樣的:
- 將搜索結(jié)果中的關(guān)鍵字用標(biāo)簽標(biāo)記出來
- 在頁面中給標(biāo)簽添加css樣式
GET /hotel/_search{
"query" : {
"match" : {
"FIELD":"TEXT"
}
},
"highlight":{
"fields " : { //指定要高亮的字段
"FIELD":{
"pre_tags": "<em>",//用來標(biāo)記高亮字段的前置標(biāo)簽
"post_tags": "</em>"//用來標(biāo)記高亮字段的后置標(biāo)簽
}
}
}
}
例如:
# 高亮查詢,默認(rèn)情況下,ES搜索字段必須與高亮字段一致
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"require_field_match": "false"
}
}
}
}
搜索結(jié)果處理整體語法:
RestClient查詢文檔
快速入門
查詢所有
@Test
void testMatchAll() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
request.source().query(QueryBuilders.matchAllQuery());
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println(response);
}
結(jié)果解析
@Test
void testMatchAll() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
request.source().query(QueryBuilders.matchAllQuery());
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結(jié)果
SearchHits searchHits = response.getHits();
// 4.1.查詢的總條數(shù)
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "條數(shù)據(jù)");
// 4.2.查詢的結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
System.out.println(json);
}
}
全文檢索查詢
全文檢索的match和multi_match查詢與match_all的API基本一致。差別是查詢條件,也就是query的部分。
@Test
void testMatch() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
request.source().query(QueryBuilders.matchQuery("all", "如家"));
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結(jié)果
handleResponse(response);
}
- IDEA中選中一段代碼后
ctrl
+alt
+M
可以將其抽取為方法
private void handleResponse(SearchResponse response) {
// 4.解析結(jié)果
SearchHits searchHits = response.getHits();
// 4.1.查詢的總條數(shù)
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "條數(shù)據(jù)");
// 4.2.查詢的結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
System.out.println(json);
}
}
精確查詢
精確查詢常見的有term查詢和range查詢,同樣利用QueryBuilders實(shí)現(xiàn):
//詞條查詢
QueryBuilders.termQuery ("city", "杭州");
//范圍查詢
QueryBuilders.rangeQuery("price").gte(100).lte(150);
復(fù)合查詢-boolean query
GET /hotel/_search{
"query" : {
"bool" : {
"must" : [
{
"term" : { "city" :"杭州"}}
],
"filter" : [
{
"range" : {
"price": { "lte" : 250 }
}
}
]
}
}
}
可寫作:
//創(chuàng)建布爾查詢
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//添加must條件
boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
//添加filter條件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
@Test
void testBool() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
// 2.1.準(zhǔn)備BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.2.添加term
boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
// 2.3.添加range
boolQuery.must(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結(jié)果
handleResponse(response);
}
排序和分頁
搜索結(jié)果的排序和分頁是與query同級(jí)的參數(shù),對應(yīng)的API如下:
@Test
void testPageAndSort() throws IOException {
//頁碼,每頁大小
int page = 1;
int size = 5;
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
// 2.1.查詢query
request.source().query(QueryBuilders.matchAllQuery());
// 2.2.分頁from、size
request.source().from((page - 1) * size).size(5);
// 2.3.排序sort
request.source().sort("price", SortOrder.ASC);
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結(jié)果
handleResponse(response);
}
高亮
高亮結(jié)果解析
@Test
void testHighlight() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
// 2.1.查詢query
request.source().query(QueryBuilders.matchQuery("all", "如家"));
// 2.2.高亮highlight
request.source().highlighter(new HighlightBuilder()
.field("name")
//是否需要與查詢字段匹配
.requireFieldMatch(false));
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結(jié)果
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
// 4.解析結(jié)果
SearchHits searchHits = response.getHits();
// 4.1.查詢的總條數(shù)
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "條數(shù)據(jù)");
// 4.2.查詢的結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//獲取高亮結(jié)果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
// if (!(highlightFields == null || highlightFields.size() == 0))
if (!CollectionUtils.isEmpty(highlightFields)) {
//根據(jù)字段名獲取高亮結(jié)果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
//獲取高亮值
String name = highlightField.getFragments()[0].string();
//覆蓋非高亮結(jié)果
hotelDoc.setName(name);
}
}
System.out.println(hotelDoc);
}
}
黑馬旅游案例
搜索和分頁
案例1:實(shí)現(xiàn)黑馬旅游的酒店搜索功能,完成關(guān)鍵字搜索和分頁
先實(shí)現(xiàn)其中的關(guān)鍵字搜索功能,實(shí)現(xiàn)步驟如下:
- 定義實(shí)體類,接收前端請求
- 定義controller接口,接收頁面請求,調(diào)用lHotelService的search方法
- 定義IHotelService中的search方法,利用match查詢實(shí)現(xiàn)根據(jù)關(guān)鍵字搜索酒店信息
步驟1:定義類,接收前端請求參數(shù)
@Data
public class RequestParams {
private String key;
private Integer page;
private Integer size;
private String sortBy;
}
@Data
public class PageResult {
private Long total;
public PageResult() {
}
public PageResult(Long total, List<HotelDoc> hotels) {
this.total = total;
this.hotels = hotels;
}
private List<HotelDoc> hotels;
}
步驟2:定義controller接口,接收前端請求
定義一個(gè)HotelController,聲明查詢接口,滿足下列要求:
- 請求方式:Post
- 請求路徑:/hotel/list
- 請求參數(shù):對象,類型為RequestParam
- 返回值: PageResult,包含兩個(gè)屬性
- Long total:總條數(shù)
- List hotels:酒店數(shù)據(jù)
@MapperScan("cn.itcast.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
@Bean
public RestHighLevelClient client() {
return new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.5.131:9200")));
}
}
步驟3:定義IHotelService中的search方法,利用match查詢實(shí)現(xiàn)根據(jù)關(guān)鍵字搜索酒店信息
@RestController
@RequestMapping("/hotel")
public class HotelController {
@Autowired
private HotelService hotelService;
@PostMapping("/list")
public PageResult search(@RequestBody RequestParams params) {
return hotelService.search(params);
}
}
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
private PageResult handleResponse(SearchResponse response) {
// 4.解析結(jié)果
SearchHits searchHits = response.getHits();
// 4.1.查詢的總條數(shù)
long total = searchHits.getTotalHits().value;
// 4.2.查詢的結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
// 4.3遍歷
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
hotels.add(hotelDoc);
}
return new PageResult(total, hotels);
}
@Override
public PageResult search(RequestParams params) {
try {
//1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
//2.準(zhǔn)備DSL
//2.1.關(guān)鍵字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
request.source().query(QueryBuilders.matchAllQuery());
} else {
request.source().query(QueryBuilders.matchQuery("all", key));
}
//2.2.分頁
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
//2.3.排序
String sortBy = params.getSortBy();
if (!(sortBy == null || "".equals(sortBy))) {
request.source().sort(sortBy, SortOrder.ASC);
}
//3.發(fā)送請求,得到響應(yīng)
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析響應(yīng)
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
條件過濾
案例2:添加品牌、城市、星級(jí)、價(jià)格等過濾功能
步驟:
-
修改RequestParams類,添加brand、city、starName、minPrice、maxPrice等參數(shù)
@Data public class RequestParams { private String key; private Integer page; private Integer size; private String sortBy; private String city; private Integer minPrice; private Integer maxPrice; }
-
修改search方法的實(shí)現(xiàn),在關(guān)鍵字搜索時(shí),如果brand等參數(shù)存在,對其做過濾
過濾條件包括:
- city精確匹配
- brand精確匹配
- starName精確匹配
- price范圍過濾
注意事項(xiàng):
- 多個(gè)條件之間是AND關(guān)系,組合多條件用BooleanQuery
- 參數(shù)存在才需要過濾,做好非空判斷
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
private PageResult handleResponse(SearchResponse response) {
// 4.解析結(jié)果
SearchHits searchHits = response.getHits();
// 4.1.查詢的總條數(shù)
long total = searchHits.getTotalHits().value;
// 4.2.查詢的結(jié)果數(shù)組
SearchHit[] hits = searchHits.getHits();
// 4.3遍歷
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hits) {
//4.3.得到source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
hotels.add(hotelDoc);
}
return new PageResult(total, hotels);
}
@Override
public PageResult search(RequestParams params) {
try {
//1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
//2.準(zhǔn)備DSL
//2.1query
buildBasicQuery(params, request);
//2.2.分頁
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
//2.3.排序
String sortBy = params.getSortBy();
if (!(sortBy == null || "".equals(sortBy) || "default".equals(sortBy))) {
request.source().sort(sortBy, SortOrder.ASC);
}
//3.發(fā)送請求,得到響應(yīng)
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析響應(yīng)
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void buildBasicQuery(RequestParams params, SearchRequest request) throws IOException {
//構(gòu)建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//關(guān)鍵字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
//city精確匹配
String city = params.getCity();
if (!(city == null || "".equals(city))) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
//brand精確匹配
String brand = params.getBrand();
if (!(brand == null || "".equals(brand))) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
//startName精確查詢
String startName = params.getStartName();
if (!(startName == null || "".equals(startName))) {
boolQuery.filter(QueryBuilders.termQuery("startName", startName));
}
//價(jià)格
Integer minPrice = params.getMinPrice();
Integer maxPrice = params.getMaxPrice();
if (minPrice != null && maxPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
}
request.source().query(boolQuery);
}
}
距離排序
案例3:我附近的酒店
前端頁面點(diǎn)擊定位后,會(huì)將你所在的位置發(fā)送到后臺(tái):
我們要根據(jù)這個(gè)坐標(biāo),將酒店結(jié)果按照到這個(gè)點(diǎn)的距離升序排序。實(shí)現(xiàn)思路如下:
-
修改RequestParams參數(shù),接收location字段
@Data public class RequestParams { private String key; private Integer page; private Integer size; private String sortBy; private String brand; private String startName; private String city; private Integer minPrice; private Integer maxPrice; private String location; }
-
修改search方法業(yè)務(wù)邏輯,如果location有值,添加根據(jù)geo_distance排序的功能
@Service public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService { @Autowired private RestHighLevelClient client; private PageResult handleResponse(SearchResponse response) { // 4.解析結(jié)果 SearchHits searchHits = response.getHits(); // 4.1.查詢的總條數(shù) long total = searchHits.getTotalHits().value; // 4.2.查詢的結(jié)果數(shù)組 SearchHit[] hits = searchHits.getHits(); // 4.3遍歷 List<HotelDoc> hotels = new ArrayList<>(); for (SearchHit hit : hits) { //4.3.得到source String json = hit.getSourceAsString(); //反序列化 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); } return new PageResult(total, hotels); } @Override public PageResult search(RequestParams params) { try { //1.準(zhǔn)備Request SearchRequest request = new SearchRequest("hotel"); //2.準(zhǔn)備DSL //2.1query buildBasicQuery(params, request); //2.2.分頁 int page = params.getPage(); int size = params.getSize(); request.source().from((page - 1) * size).size(size); //2.3.排序 //距離 String location = params.getLocation(); if (!(location == null || "".equals(location))) { request.source().sort(SortBuilders .geoDistanceSort("location", new GeoPoint(location)) .order(SortOrder.ASC) .unit(DistanceUnit.KILOMETERS) ); } //排序方式 String sortBy = params.getSortBy(); if (!(sortBy == null || "".equals(sortBy) || "default".equals(sortBy))) { request.source().sort(sortBy, SortOrder.ASC); } //3.發(fā)送請求,得到響應(yīng) SearchResponse response = client.search(request, RequestOptions.DEFAULT); //4.解析響應(yīng) return handleResponse(response); } catch (IOException e) { throw new RuntimeException(e); } } private void buildBasicQuery(RequestParams params, SearchRequest request) throws IOException { //構(gòu)建BooleanQuery BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //關(guān)鍵字搜索 String key = params.getKey(); if (key == null || "".equals(key)) { boolQuery.must(QueryBuilders.matchAllQuery()); } else { boolQuery.must(QueryBuilders.matchQuery("all", key)); } //city精確匹配 String city = params.getCity(); if (!(city == null || "".equals(city))) { boolQuery.filter(QueryBuilders.termQuery("city", city)); } //brand精確匹配 String brand = params.getBrand(); if (!(brand == null || "".equals(brand))) { boolQuery.filter(QueryBuilders.termQuery("brand", brand)); } //startName精確查詢 String startName = params.getStartName(); if (!(startName == null || "".equals(startName))) { boolQuery.filter(QueryBuilders.termQuery("startName", startName)); } //價(jià)格 Integer minPrice = params.getMinPrice(); Integer maxPrice = params.getMaxPrice(); if (minPrice != null && maxPrice != null) { boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice)); } request.source().query(boolQuery); } }
廣告置頂
案例4:讓指定的酒店在搜索結(jié)果中排名置頂
我們給需要置頂?shù)木频晡臋n添加一個(gè)標(biāo)記。然后利用function score給帶有標(biāo)記的文檔增加權(quán)重。
實(shí)現(xiàn)步驟分析:
-
給HotelDoc類添加isAD字段,Boolean類型
private Long id; private String name; private String address; private Integer price; private Integer score; private String brand; private String city; private String starName; private String business; private String location; private String pic; private Object distance; private Boolean idAD;
-
挑選幾個(gè)你喜歡的酒店,給它的文檔數(shù)據(jù)添加isAD字段,值為true
POST /hotel/_update/1931442052 { "doc": { "isAD":true } } POST /hotel/_update/1584362548 { "doc": { "isAD":true } } POST /hotel/_update/1630005459 { "doc": { "isAD":true } } POST /hotel/_update/1880614409 { "doc": { "isAD":true } } POST /hotel/_update/1908594080 { "doc": { "isAD":true } }
-
修改search方法,添加function score功能,給isAD值為true的酒店增加權(quán)重文章來源:http://www.zghlxwxcb.cn/news/detail-450347.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-450347.html
private void buildBasicQuery(RequestParams params, SearchRequest request) throws IOException {
//1.構(gòu)建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//關(guān)鍵字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
//city精確匹配
String city = params.getCity();
if (!(city == null || "".equals(city))) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
//brand精確匹配
String brand = params.getBrand();
if (!(brand == null || "".equals(brand))) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
//startName精確查詢
String startName = params.getStartName();
if (!(startName == null || "".equals(startName))) {
boolQuery.filter(QueryBuilders.termQuery("startName", startName));
}
//價(jià)格
Integer minPrice = params.getMinPrice();
Integer maxPrice = params.getMaxPrice();
if (minPrice != null && maxPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
}
//2.算分控制
FunctionScoreQueryBuilder functionScoreQuery =
QueryBuilders.functionScoreQuery(
//原始查詢,相關(guān)性算分的查詢
boolQuery,
//function score的數(shù)組
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
//其中的一個(gè)function score元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
//過濾條件
QueryBuilders.termQuery("isAD", true),
//算分函數(shù)
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
}
到了這里,關(guān)于分布式搜索引擎——elasticsearch搜索功能的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!