一、前言
之前我們學(xué)習(xí)了布爾查詢,知道了filter查詢只在乎查詢條件和文檔的匹配程度,但不會根據(jù)匹配程度對文檔進行打分,而對于must、should這兩個布爾查詢會對文檔進行打分,那如果我想在查詢的時候同時不去在乎文檔的打分(對搜索結(jié)果的排序),只想過濾文本字段是否包含這個詞,除了filter查詢,我們還會介紹Constant Score查詢。相反,如果想干預(yù)這個分?jǐn)?shù),我們會使用Function Score查詢,這些都會在后面介紹到。
二、Constant Score查詢
如果不想讓檢索詞頻率TF(Term Frequency)對搜索結(jié)果排序有影響,只想過濾某個文本字段是否包含某個詞,可以使用Constant Score將查詢語句包裝起來。
假設(shè)需要查詢city字段是否包含關(guān)鍵詞“上海”的酒店,則請求的DSL如下:
POST /hotel/_search
{
"query": {
"constant_score": { //滿足條件即打分為1(默認(rèn)值是1)
"filter": {
"term": { //term查詢city中是上海的城市
"city": "上海"
}
}
}
}
}
查詢結(jié)果如下:
{
...
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "004",
"_score" : 1.0,
"_source" : {
"title" : "京盛集團酒店",
"city" : "上海",
"price" : "800.00",
"create_time" : "2021-05-29 21:35:00",
"amenities" : "浴池(假日需預(yù)訂),室內(nèi)游泳池,普通停車場/充電停車場",
"full_room" : true,
"location" : {
"lat" : 36.940243,
"lon" : 120.394
},
"praise" : 100
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "006",
"_score" : 1.0,
"_source" : {
"title" : "京盛集團精選酒店",
"city" : "上海",
"price" : "500.00",
"create_time" : "2022-01-29 22:50:00",
"full_room" : true,
"location" : {
"lat" : 40.918229,
"lon" : 118.422011
},
"praise" : 20
}
}
]
}
}
通過結(jié)果可以看到,使用Constant Score搜索時,命中的酒店文檔對應(yīng)的city字段都包含“上?!币辉~。但是不論該詞在文檔中出現(xiàn)多少次,這些文檔的得分都是一樣的1.0.
PS:很多人可能會把constant_score查詢中的filter和布爾查詢的filter搞混,constant_score中的filter可以把它想象成普通的query,它后面接的就是各種各樣的查詢子句。如term,terms,exists,bool等等。
比如我想同時使用must查詢創(chuàng)建時間大于等于2022-01-29 22:50:00的hotel且不在乎打分,那么可以使用下面的DSL:
POST /hotel/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"must": [
{
"range": {
"create_time": {
"gte": "2022-01-29 22:50:00"
}
}
}
]
}
}
}
}
}
在Constant Score搜索中,參數(shù)boost可以控制命中文檔的得分,默認(rèn)值都是1.0,以下為更改boost參數(shù)為2.0的例子:
POST /hotel/_search
{
"query": {
"constant_score": {
"boost":2.0,
"filter": {
"term": {
"city": "上海"
}
}
}
}
}
查詢結(jié)果如下:
{
...
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 2.0,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "004",
"_score" : 2.0,
"_source" : {
"title" : "京盛集團酒店",
"city" : "上海",
"price" : "800.00",
"create_time" : "2021-05-29 21:35:00",
"amenities" : "浴池(假日需預(yù)訂),室內(nèi)游泳池,普通停車場/充電停車場",
"full_room" : true,
"location" : {
"lat" : 36.940243,
"lon" : 120.394
},
"praise" : 100
}
},
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "006",
"_score" : 2.0,
"_source" : {
"title" : "京盛集團精選酒店",
"city" : "上海",
"price" : "500.00",
"create_time" : "2022-01-29 22:50:00",
"full_room" : true,
"location" : {
"lat" : 40.918229,
"lon" : 118.422011
},
"praise" : 20
}
}
]
}
}
根據(jù)搜索結(jié)果可以看到,設(shè)定Boost值為2.0后,所有的命中的文檔得分都為2.0。
然后對于Constant Score的效率問題,我們拿它和上一節(jié)講到的filter查詢做一個對比:
- Constant Score查詢實際上就是一個沒有分值函數(shù)的查詢,它會將所有匹配文檔的分值設(shè)置為一個常量。這種查詢不需要計算每個匹配文檔的相關(guān)度,所以效率會比普通查詢高。
- 但是Constant Score查詢還需要執(zhí)行查詢本身,比如匹配查詢條件、過濾文檔等步驟。而filter查詢僅僅過濾文檔,不計算分值,所以整體效率比Constant Score查詢更高。
- Constant Score查詢不會像filter查詢那樣緩存過濾結(jié)果。因為Constant Score查詢還需要計算每個匹配文檔的分值,而這一步不受過濾結(jié)果緩存的影響。
- 所以總的來說,在效率方面: filter查詢 > Constant Score查詢 > 普通查詢
在java客戶端上構(gòu)建Constant Score搜索時,可以使用ConstantScoreQueryBuilder類的實例進行構(gòu)建,它接收一個QueryBuilder參數(shù),即可以接收termQueryBuilder,termsQueryBuilder,boolQueryBuilder等等,和之前的DSL是一樣的,那么比如我們查詢一個城市是上?;蛘弑本┑木频?,代碼如下:
Service層,getQueryResult()可以看往期的博客,有具體的方法實現(xiàn):
public List<Hotel> constantScore(HotelDocRequest hotelDocRequest) throws IOException {
//新建搜索請求
String indexName = getNotNullIndexName(hotelDocRequest);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermQueryBuilder termQueryBuilder1 = QueryBuilders.termQuery("city", "北京");
TermQueryBuilder termQueryBuilder2 = QueryBuilders.termQuery("city", "上海");
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(termQueryBuilder1).should(termQueryBuilder2);
//構(gòu)建ConstantScoreBuilder
ConstantScoreQueryBuilder constantScoreQueryBuilder = new ConstantScoreQueryBuilder(boolQueryBuilder);
//設(shè)置固定分?jǐn)?shù)2.0
constantScoreQueryBuilder.boost(2.0f);
searchSourceBuilder.query(constantScoreQueryBuilder);
searchRequest.source(searchSourceBuilder);
return getQueryResult(searchRequest);
}
Controller層:
@PostMapping("/query/constant_score")
public FoundationResponse<List<Hotel>> constantScoreQuery(@RequestBody HotelDocRequest hotelDocRequest) {
try {
List<Hotel> hotelList = esQueryService.constantScore(hotelDocRequest);
if (CollUtil.isNotEmpty(hotelList)) {
return FoundationResponse.success(hotelList);
} else {
return FoundationResponse.error(100,"no data");
}
} catch (IOException e) {
log.warn("搜索發(fā)生異常,原因為:{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
} catch (Exception e) {
log.error("服務(wù)發(fā)生異常,原因為:{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
}
}
Postman實現(xiàn):
三、Function Score查詢
當(dāng)你使用ES進行搜索時,命中的文檔默認(rèn)按照相關(guān)度進行排序,有些場景下用戶需要干預(yù)該“相關(guān)度”,此時就可以使用Function Score查詢。使用時,用戶必須定義一個查詢以及一個或多個函數(shù),這些函數(shù)為每一個文檔計算一個新分?jǐn)?shù)。
它允許每個主查詢query匹配的文檔應(yīng)用加強函數(shù),以達到改變原始查詢評分_score的目的。
3.1、function_score 查詢模板
function_score 查詢模板可以分為兩類,分別為單個加強函數(shù)的查詢和多個加強函數(shù)的查詢。
單個加強函數(shù)的查詢模板:
{
"query": {
"function_score": {
"query": {.....}, // 主查詢,查詢完后會有一個 _score 評分
"field_value_factor": {...}, // 在 _score 的基礎(chǔ)上進行強化評分
"boost_mode": "multiply", // 指定用哪種方式結(jié)合 _score 和 強化 score
"max_boost": 1.5 // 限制強化 score 的最高分,但是不會限制 _score
}
}
}
多個加強函數(shù)的查詢模板:
{
"query": {
"function_score": {
"query": {.....},
"functions": [ // 可以有多個加強函數(shù)(或是 filter+加強函數(shù)),每一個加強函數(shù)會產(chǎn)生一個加強 score
{ "field_value_factor": ... },
{ "gauss": ... },
{ "filter": {...}, "weight": ... }
],
"score_mode": "sum", // 決定加強 score 們?nèi)绾握?/span>
"boost_mode": "multiply" // 決定最后的 functions 中 score 和 query score 的結(jié)合方式
}
}
}
3.2、function_score 參數(shù)
強化 _score 計算的函數(shù)
function_score 提供了幾種內(nèi)置加強 _score 計算的函數(shù)功能:
-
weight
:設(shè)置一個簡單而不被規(guī)范化的權(quán)重提升值。
weight 加強函數(shù)和 boost 參數(shù)比較類似,可以用于任何查詢,不過有一點差別是 weight 不會被 Lucene 規(guī)范化(normalize)成難以理解的浮點數(shù),而是直接被應(yīng)用。
例如,當(dāng) weight 為 2 時,最終得分為 new_score = 2 * _score
。
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"weight":2
}
}
}
輸出后可以對比一下不加weight的默認(rèn)分?jǐn)?shù),基本分?jǐn)?shù)都翻了2倍
-
field_value_factor
:指定文檔中某個字段的值結(jié)合 _score 改變分?jǐn)?shù)
屬性如下:field
:指定字段名factor
:對字段值進行預(yù)處理,乘以(或者加,取決于boost_mode)指定的數(shù)值(默認(rèn)為1)modifier
:將字段值進行加工,有以下的幾個選項:
-
none
:不處理 -
log
:計算對數(shù) -
log1p
:先將字段值+1,再計算對數(shù) -
log2p
:先將字段值+2,再計算對數(shù) -
ln
:計算自然對數(shù) -
ln1p
:先將字段值+1,再計算自然對數(shù) -
ln2p
:先將字段值+2,再計算自然對數(shù) -
square
:計算平方 -
sqrt
:計算平方根 -
reciprocal
:計算倒數(shù)
{
"query": {
"function_score": {
"query": {.....},
"field_value_factor": {
"field": "price",
"modifier": "none",
"factor": 1.2
},
"boost_mode": "multiply",
"max_boost": 1.5
}
}
}
調(diào)整后的 function 分?jǐn)?shù)公式為,factor * doc['price'].value
;如果boos_mode設(shè)定為sum,那么分?jǐn)?shù)公式為factor + doc['price'].value
;
例如我們讓最終的分?jǐn)?shù)以price字段進行增強,在原分?jǐn)?shù)基礎(chǔ)上*1.2
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"field_value_factor": {
"field":"price",
"factor": 1.2
},
"boost_mode": "multiply"
}
}
}
再例如我想對字段值先乘1.2再+1再取對數(shù),那么DSL如下:
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"field_value_factor": {
"field":"price",
"modifier": "ln1p",
"missing":1.0,
"factor": 1.2
},
"boost_mode": "multiply"
}
}
}
function 分?jǐn)?shù)為,ln1p(1.2 * doc['view_cnt'].value)
,如果指定字段缺失用 missing 對應(yīng)的值,至于和匹配的相關(guān)性分?jǐn)?shù) _score 如何結(jié)合需要下面的 boost_mode 參數(shù)來決定。
-
random_score
:使用一致性隨機分值計算來對每個用戶采用不同的結(jié)果排序方式,對相同用戶仍然使用相同的排序方式,其本質(zhì)上用的是seed 種子參數(shù),用戶相關(guān)的 id 與 seed 構(gòu)造映射關(guān)系,就可千人千面的效果,seed 不同排序結(jié)果也不同。具體示例如下:
①字段值相同,例如通過full_room,由上面查詢結(jié)果可知,兩個結(jié)果的full_room相同,此時使用random_score,兩個的排序結(jié)果仍然是一致的:
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"random_score": {
"field":"full_room",
"seed": 10
},
"boost_mode": "multiply"
}
}
}
如果對price進行隨機加強,那么排序就會不一樣:
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"random_score": {
"field":"price",
"seed": 10
},
"boost_mode": "multiply"
}
}
}
我們可以調(diào)整seed,就會發(fā)現(xiàn)排序不一樣。
-
衰減函數(shù)
(decay function):es 內(nèi)置了三種衰減函數(shù),分別是 linear、exp 和 gauss;
三種衰減函數(shù)的差別只在于衰減曲線的形狀,在 DSL 的語法上的用法完全一樣;linear
: 線性函數(shù)是條直線,一旦直線與橫軸0香蕉,所有其他值的評分都是0exp
: 指數(shù)函數(shù)是先劇烈衰減然后變緩guass
(最常用) : 高斯函數(shù)則是鐘形的,他的衰減速率是先緩慢,然后變快,最后又放緩
origin
:中心點 或字段可能的最佳值,落在原點 origin 上的文檔評分 _score 為滿分 1.0 。
scale
:衰減率,即一個文檔從原點 origin 下落時,評分 _score 改變的速度。(例如,每 £10 歐元或每 100 米)。
decay
:從原點 origin 衰減到 scale 所得的評分 _score ,默認(rèn)值為 0.5 。
offset
:以原點 origin 為中心點,為其設(shè)置一個非零的偏移量 offset 覆蓋一個范圍,而不只是單個原點。在范圍 -offset <= origin <= +offset 內(nèi)的所有評分 _score 都是 1.0 。不設(shè)置默認(rèn)是0
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"gauss": {
"price": {
// 如果不設(shè)置offset,offset默認(rèn)為0 公式 : origin-offset <= value <= origin+offset
// 范圍在800-0 <= value <= 800+0的文檔的評分_score都是滿分1.0
//而在此范圍之外,評分會開始衰減,衰減率由scale值(此處是300)和decay值(此處是0.2)決定
// 也就是說,在origin + offset + scale或是origin - offset - scale的點上,得到的分?jǐn)?shù)僅有decay分
"origin": "800",
"scale": "300",
"decay": 0.2
}
},
"boost_mode": "multiply"
}
}
}
對衰減函數(shù)感興趣的小伙伴可以瀏覽這篇文章,講的很詳細,尤其是最后對于用戶同時對于酒店的地理位置和價格去做一個篩選。
-
script_score
:當(dāng)需求超出以上范圍時,可以用自定義腳本完全控制評分計算。
3.3、其它輔助函數(shù)
-
boost_mode
參數(shù):決定 query 中的相關(guān)性分?jǐn)?shù)和加強的函數(shù)分?jǐn)?shù)的結(jié)合方式。
multiply
:默認(rèn)的配置,兩者分?jǐn)?shù)相乘,new_score = _score * boost_score;sum
:兩者相加,new_score = _score + boost_score;min
:取兩者最小值,new_score = min(_score, boost_score);max
:取兩者最大值,new_score = max(_score, boost_score);replace
:用 boost_score 替換 _score 值。有時候我們可以通過replace看具體的函數(shù)得分是多少,便于我們排查問題
-
score_mode
參數(shù)決定 functions 里面的強化 score 如何結(jié)合
function_score 先會執(zhí)行 score_mode 的設(shè)置,即先整合所有的強化計算,再執(zhí)行 boost_mode 的配置,就是將 query 相關(guān)性分?jǐn)?shù)和整合強化分?jǐn)?shù)的結(jié)合。multiply
:默認(rèn)的配置,多個強化分?jǐn)?shù)相乘;sum
:多個強化分?jǐn)?shù)相加;min
:取多個強化分?jǐn)?shù)最小值;max
:取多個強化分?jǐn)?shù)最大值;avg
:取多個強化分?jǐn)?shù)平均值;first
:使用首個函數(shù)的結(jié)果作為最終結(jié)果。
-
max_boost
:限制加強函數(shù)的最大效果,就是限制加強 score 最大能多少,但要注意不會限制 old_score。
如果加強 score 超過了 max_boost 限制的值,會把加強 score 的值設(shè)成 max_boost 的值;
假設(shè)加強 score 是5,而 max_boost 是2,因為加強 score 超出了 max_boost 的限制,所以 max_boost 就會把加強 score 改為2。簡單的說,就是 final_score = min(整合后的 score, max_boost)。
3.4、java實現(xiàn)
funtion_score的參數(shù)我們可以通過ScoreFunctionBuilders.xxx構(gòu)筑
Service層實現(xiàn):
public List<Hotel> functionScoreScore(HotelDocRequest hotelDocRequest) throws IOException {
//新建搜索請求
String indexName = getNotNullIndexName(hotelDocRequest);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("city", "上海");
//構(gòu)建FunctionScoreBuilder,比如這里構(gòu)筑高斯函數(shù)(衰減函數(shù))
GaussDecayFunctionBuilder gaussDecayFunctionBuilder = ScoreFunctionBuilders.gaussDecayFunction(hotelDocRequest.getPropertiesName(), 800, 200, 0, 0.2);
//構(gòu)建Function Score查詢
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(termQueryBuilder, gaussDecayFunctionBuilder).boostMode(CombineFunction.MULTIPLY);
searchSourceBuilder.query(functionScoreQueryBuilder);
searchRequest.source(searchSourceBuilder);
return getQueryResult(searchRequest);
}
controller層實現(xiàn):文章來源:http://www.zghlxwxcb.cn/news/detail-666826.html
@PostMapping("/query/function_score")
public FoundationResponse<List<Hotel>> functionScoreQuery(@RequestBody HotelDocRequest hotelDocRequest) {
try {
List<Hotel> hotelList = esQueryService.functionScoreScore(hotelDocRequest);
if (CollUtil.isNotEmpty(hotelList)) {
return FoundationResponse.success(hotelList);
} else {
return FoundationResponse.error(100,"no data");
}
} catch (IOException e) {
log.warn("搜索發(fā)生異常,原因為:{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
} catch (Exception e) {
log.error("服務(wù)發(fā)生異常,原因為:{}", e.getMessage());
return FoundationResponse.error(100, e.getMessage());
}
}
postman實現(xiàn)截圖:文章來源地址http://www.zghlxwxcb.cn/news/detail-666826.html
到了這里,關(guān)于Elasticsearch(十三)搜索---搜索匹配功能④--Constant Score查詢、Function Score查詢的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!