Sort 查詢排序
測試索引
Elasticsearch針對普通數據、數組、嵌套對象、地理位置都提供了排序功能,爲了測試其排序方式我們需要可以能夠足夠數據類型的索引。所以創建了下面一個索引以及其映射
PUT offline_sales
PUT offline_sales/_mapping
{
"properties": {
"order_id": {
"type": "keyword"
},
"products": {
"type": "nested",
"properties": {
"quantity": {
"type": "long"
},
"price": {
"type": "half_float"
},
"product_id": {
"type": "long"
}
}
},
"total_price": {
"type": "half_float"
},
"price_list": {
"type": "half_float"
},
"geo_location": {
"type": "geo_point"
},
"sale_date": {
"type": "date",
"format": [
"yyyy-MM-dd HH:mm:ss"
]
}
}
}
現在插入測試數據
{"index":{"_index":"offline_sales","_id":0}}
{"order_id": "1","products": [{"product_id": 1,"price": 10.0,"quantity": 1},{"product_id": 2,"price": 20.0,"quantity": 2},{"product_id": 3,"price": 30.0,"quantity": 3}],"total_price": 140.0,"price_list":[10.0,20.0,30.0],"geo_location":[114.3185806274,30.5647434479],"sale_date": "2019-12-22 22:22:29","comment_num": 0}
{"index":{"_index":"offline_sales","_id":1}}
{"order_id": "2","products": [{"product_id": 1,"price": 10.0,"quantity": 1},{"product_id": 2,"price": 40.0,"quantity": 2},{"product_id": 3,"price": 30.0,"quantity": 3}],"total_price": 180.0,"price_list":[10.0,40.0,30.0],"geo_location":[114.3185806274,30.5847434479],"sale_date": "2019-12-22 22:22:29","comment_num": 0}
......
需要注意,國內地圖獲取的地理位置數組爲[緯度,經度],而ES的地理位置座標要求輸入[經度,緯度]
使用元數據排序
Elasticsearch除了允許其使用索引中的字段進行排序,還提供了了_score
以及_doc
數據的排序。其中_score
基於查詢匹配的分數排序,而_doc
是根據索引的順序自然排序。
官方文檔中也強調。_doc
排序是最有效的排序,在不需要根據某些業務實現文檔順序,僅僅是想實現某些翻頁效果的時候,使用_doc
排序會比較好。
排序後的返回結果
在使用非元數據進行排序的時候(文檔字段)參與排序的字段值會作爲結果的一部分輸出。
需要注意的是,對於某些類型數據(比如日期類型,返回的數據並不是原始的年月日類型的數據)
對文檔內容根據日期進行排序
GET /offline_sales/_search
{
"sort" : [
"sale_date"
]
}
最後返回的排序字段值已經被轉換。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
......
},
"hits" : {
"total" : {......},
"max_score" : null,
"hits" : [
{
"_index" : "offline_sales",
"_type" : "_doc",
"_id" : "12",
"_score" : null,
"_source" : {......},
"sort" : [
1577053349000
]
}
]
}
}
不同情況下的排序參數
多字段排序
sort
支持設置多個字段參與排序
GET /offline_sales/_search
{
"sort" : [
{ "sale_date" : {"order" : "asc"}},
"total_price",
{ "order_id" : "desc" },
"_score"
]
}
"sort" : [
1577053349000,
140.0,
"9",
1.0
]
跨索引不同類型字段排序
Elasticsearch在進行跨索引查詢的時候也可以設置排序策略,不過此時要是排序字段的類型一致或者相似。對於數值類型的字段可以使用numeric_type選項將值從一種類型轉換爲另一種類型。
由於字段在第一個索引中映射爲double,在第二個索引中映射爲long,因此不能使用該字段對默認情況下同時查詢兩個索引的請求進行排序。但是,您可以使用numeric_type選項強制類型爲一個或另一個,以便強制爲所有索引的特定類型:
注意正常情況下上面的例子應該轉換爲double,但是爲了突出顯示數據被轉換了,所以使用了long進行表示。
numeric_type支持的參數爲:[“double”, “long”, “date”, “date_nanos”]
以這兩個指標爲例:
GET /offline_sales,online_sales/_search
{
"sort": [
{
"total_price": {
"numeric_type": "long"
}
}
]
}
"sort" : [
140
]
不同時間類型排序
numeric_type
還可用於將使用毫秒分辨率的日期字段轉換爲具有納秒分辨率的date_nanos字段。以這兩個指標爲例:使用numeric_type類型選項可以爲排序設置單個分辨率,設置到日期將把date_nanos轉換爲毫秒分辨率,而date_nanos將把日期字段的值轉換爲納秒分辨率:
GET /offline_sales,online_sales/_search
{
"sort": [
{
"sale_date": {
"numeric_type": "date_nanos"
}
}
]
}
需要注意的是,因爲使用long類型計算時間的納秒數值,爲了避免溢出,對date_nanos的轉換不能應用於1970年以前和2262年以後的日期。
數組類型字段排序
針對字段爲數組結構的數據,sort
提供了根據mode
設置類型進行排序的策略
參數 | 說明 |
---|---|
min | 基於數組中最小值進行排序,如果不是數組就取當前值 |
max | 基於數組中最大值進行排序,如果不是數組就取當前值 |
sum | 使用所有值的和作爲排序值。僅適用於基於數字的數組字段 |
avg | 使用所有值的平均值作爲排序值。僅適用於基於數字的數組字段 |
median | 使用所有值的中位數作爲排序值。僅適用於基於數字的數組字段 |
下面的例子中價格是個列表結構,而通過設置"mode" : "min"
,ES會根據此字段數組中最小的那個值進行排序
GET offline_sales/_search
{
"sort" : [
{
"price_list" : {
"mode" : "min",
"order" : "asc"
}
}
]
}
嵌套類型字段排序
Elasticsearch還支持根據一個或多個嵌套對象中的字段進行排序。支持按嵌套字段排序的嵌套排序選項具有以下屬性:
參數 | 說明 |
---|---|
path | 定義對哪個嵌套對象進行排序。實際的排序字段必須是這個嵌套對象中的直接字段。按嵌套字段排序時,必須使用此字段。 |
filter | 一個篩選器,嵌套路徑內的內部對象應與之匹配,以便通過排序考慮其字段值。常見的情況是在嵌套的篩選器或查詢中重複查詢/篩選器。默認情況下,沒有nested_filter是活動的。 |
max_children | 在選擇排序值時,每個根文檔要考慮的子文檔的最大數量。默認爲無限。 |
nested | 與頂級嵌套相同,但適用於當前嵌套對象中的另一個嵌套路徑。 |
nested_path和nested_filter選項已經被棄用,取而代之的是上面記錄的選項。
在下面的示例中,products是一個嵌套類型的字段。類似下面的結構
"products" : [
{
"product_id" : 1,
"price" : 10.0,
"quantity" : 1
},
{
"product_id" : 2,
"price" : 40.0,
"quantity" : 2
},
{
"product_id" : 3,
"price" : 30.0,
"quantity" : 3
}
]
因爲是數組,且又是嵌套類型,需要指定嵌套路徑;否則,Elasticsearch不知道需要在什麼嵌套級別上捕獲排序值。下面的內容就是根據每個訂單購買數量進行排序
GET offline_sales/_search
{
"sort" : [
{
"products.quantity" : {
"mode" : "sum",
"order" : "asc",
"nested": {
"path": "products"
}
}
}
]
}
地理距離排序
除了針對傳統數據進行排序,ES還支持根據地理位置進行排序。根據地理位置進行排序的時候需要指定一個初始座標,使用地理位置排序支持以下參數。
參數 | 說明 |
---|---|
distance_type | 如何計算距離。可以是arc(默認值),也可以是plane(更快,但在長距離和接近極點時不準確)。 |
mode | 如果一個字段有幾個地理點該怎麼做。默認情況下,升序排序時考慮最短距離,降序排序時考慮最長距離。支持的值爲min、max、median和avg。 |
unit | 計算排序值時使用的單元。默認值是m(米)。 |
ignore_unmapped | 指示是否應將未映射的字段視爲丟失的值。將其設置爲true相當於在字段sort中指定unmapped_type。默認值是false(未映射的字段導致搜索失敗)。 |
地理距離排序不支持缺失值:當文檔沒有用於距離計算的字段值時,該距離總是被認爲等於無窮大。
下面是根據之前創建的索引使用地理位置進行排序的例子。
GET offline_sales/_search
{
"sort": [
{
"_geo_distance": {
"geo_location": [
114.3185806274,
30.5647434479
],
"order": "asc",
"unit": "m",
"distance_type": "arc",
"ignore_unmapped": false
}
}
]
}
其返回的sort
字段爲初始座標點和數據中座標點的直線距離
"sort" : [
2223.897834365496
]
在進行地理位置排序的時候,初始座標點的設置並非固定,系統支持多種數據結構
對象結構
GET offline_sales/_search
{
"sort": [
{
"_geo_distance": {
"geo_location": {
"lat": 30.5647434479,
"lon": 114.3185806274
},
"order": "asc",
"unit": "m",
"distance_type": "arc",
"ignore_unmapped": false
}
}
]
}
字符串類型
GET offline_sales/_search
{
"sort": [
{
"_geo_distance": {
"geo_location": "30.5647434479,114.3185806274",
"order": "asc",
"unit": "m",
"distance_type": "arc",
"ignore_unmapped": false
}
}
]
}
Geohash
"pin.location" : "drm3btev3e86",
數組類型
上面例子。
此處需要注意字符串類型和數組類型中經緯度的值順序設置是相反的。
字段缺失以及不存在字段
字段參數缺失
如果指定的字段,缺少值得時候ES提供了三個方式來設置其邏輯。默認值是_last
- _last:置於最後
- _first:置於最前
- 自定義參數:使用自定義的內容參與排序
GET offline_sales/_search
{
"sort": [
{
"total_price": {
"missing": "_last"
}
}
]
}
字段缺失
默認情況下,如果依據一個不存在的字段進行排序,此處查詢請求會失敗,但是添加了unmapped_type
參數後查詢可以忽略沒有映射的字段,並繼續進行排序。
GET offline_sales,online_sales/_search
{
"sort": [
{
"real_price": {
"unmapped_type" : "half_float"
}
}
]
}
雖然官方文檔說明此時不會根據它們排序,但是實際上根據sort
輸出內容可以看到詞字段被設置爲了Infinity
或者-Infinity
(具體取決於你使用升序還是降序)
missing和unmapped_type配合使用
當嘗試將兩個索引合併查詢並排序的時候,如果排序字段只存在於一張索引下,可以如下面例子中,沒有此字段的索引參與排序的字段會被設置爲默認的數字參與排序。
GET offline_sales,online_sales/_search
{
"sort": [
{
"real_price": {
"unmapped_type" : "half_float",
"missing": 210.0
}
}
]
}
基於腳本的排序
排序的時候可以使用腳本來實現對排序值得修改,它並不會影響到字段的原始值而僅僅在排序中發揮作用。和之前介紹腳本使用一樣在kibana中可以使用"""
獲得一個結構清晰的腳本內容。
下面例子就是對價格低於150的訂單進行1.1倍的加成。
GET offline_sales/_search
{
"sort" : {
"_script" : {
"type" : "number",
"script" : {
"lang": "painless",
"source": """
if (doc['total_price'].value < 150.0) {
doc['total_price'].value*params.factor
} else {
doc['total_price'].value
}
""",
"params" : {
"factor" : 1.1
}
},
"order" : "asc"
}
}
}
跟蹤分數
除非是基於_score
進行排序,對其他字段進行排序的時候,結果中並不會顯示文檔得到的分數,此時假如需要獲取文檔的匹配分數的時候可以設置"track_scores": true
,這樣返回的文檔中會顯示文檔的得分
GET /offline_sales/_search
{
"track_scores": true,
"sort" : [
"_doc"
]
}
關於排序的內存消耗
關於排序,在進行排序的時候排序字段的值會被整體加載到內存中,這就需要保證有足夠的內存來保存這些數據。所以官方建議不應該對字符串類型的數據進行排序,而對於數值類型的字段進行排序的時候,儘可能的將其設置爲較短類型的數據。
個人水平有限,上面的內容可能存在沒有描述清楚或者錯誤的地方,假如開發同學發現了,請及時告知,我會第一時間修改相關內容。假如我的這篇內容對你有任何幫助的話,麻煩給我點一個贊。你的點贊就是我前進的動力。