Elasticsearch中如何进行排序(中文+父子文档+嵌套文档)
Elasticsearch中如何進(jìn)行排序
背景
最近去兄弟部門(mén)的新自定義查詢(xún)項(xiàng)目組搬磚,項(xiàng)目使用Elasticsearch進(jìn)行數(shù)據(jù)的檢索和查詢(xún)。每一個(gè)查詢(xún)頁(yè)面都需要根據(jù)選擇的字段進(jìn)行排序,以為是一個(gè)比較簡(jiǎn)單的需求,其實(shí)實(shí)現(xiàn)起來(lái)還是比較復(fù)雜的。這里進(jìn)行一個(gè)總結(jié),加深一下記憶。
前置知識(shí)
Elasticsearch是什么?
Elasticsearch 簡(jiǎn)稱(chēng)ES,是一個(gè)全文搜索引擎,可以實(shí)現(xiàn)類(lèi)似百度搜索的功能。但她不僅僅能進(jìn)行全文檢索,還可以實(shí)現(xiàn)PB級(jí)數(shù)據(jù)的近實(shí)時(shí)分析和精確查找,還可以作GIS數(shù)據(jù)庫(kù),進(jìn)行AI機(jī)器學(xué)習(xí),功能非常強(qiáng)大。ES的數(shù)據(jù)模型
ES中常用嵌套文檔和父子文檔兩種方法進(jìn)行數(shù)據(jù)建模,多層父子文檔還可以形成祖孫文檔。但是父子文檔是一種不推薦的建模方式,這種方式有很多的局限性。如果傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)的建模方法是通過(guò)“三范式”進(jìn)行規(guī)范化,那么ES的建模方法就是反范式,進(jìn)行反規(guī)范化。關(guān)系型數(shù)據(jù)庫(kù)的數(shù)據(jù)是表格模型,ES是JSON樹(shù)狀模型。
ES中排序的分類(lèi)
根據(jù)建模方法的不同,ES中排序分為以下幾種,每種都有不同的排序?qū)懛ê鸵恍┫拗?#xff1a;
- 嵌套文檔-根據(jù)主文檔字段排序
- 嵌套文檔-根據(jù)內(nèi)嵌文檔字段排序
- 父子文檔-查父文檔然后根據(jù)子文檔排序
- 父子文檔-查子文檔然后根據(jù)父文檔排序
- 更復(fù)雜的情況,父子文檔里又嵌套了文檔,然后根據(jù)嵌套文檔字段進(jìn)行排序。
下面分別對(duì)其中某幾種情況和中文字段的排序,進(jìn)行測(cè)試說(shuō)明(ES 5.5.x)。
測(cè)試數(shù)據(jù)準(zhǔn)備
首先,設(shè)置索引類(lèi)型字段映射
PUT /test_sort {"mappings": {"zf":{"properties": {"id":{"type": "keyword"},"name":{"type": "keyword"},"age":{"type": "integer"},"shgx":{"type": "nested"}}}} }然后新建測(cè)試數(shù)據(jù)
PUT /test_sort/zf/1 {"id":1,"name":"張三","age":18,"shgx":[{"id":1,"name":"老張","age":50,"gx":"父親"},{"id":2,"name":"張二","age":22,"gx":"哥哥"}] }PUT /test_sort/zf/2 {"id":2,"name":"李四","age":25,"shgx":[{"id":3,"name":"李五","age":23,"gx":"弟弟"}] }- 嵌套文檔-根據(jù)主文檔字段排序
根據(jù)zf主文檔age字段倒敘排列,直接加sort子語(yǔ)句就可以
結(jié)果:
"hits": [{"_index": "test_sort","_type": "zf","_id": "2","_score": null,"_source": {"name": "李四","id": 2,"age": 25},"sort": [25]},{"_index": "test_sort","_type": "zf","_id": "1","_score": null,"_source": {"name": "張三","id": 1,"age": 18},"sort": [18]}]- 嵌套文檔-根據(jù)內(nèi)嵌文檔字段排序
根據(jù)age小于50歲的親屬排序,理論上李四應(yīng)該排第一位,因?yàn)?0歲以下的親屬,李五最大。 憑直覺(jué)先這樣寫(xiě):
看結(jié)果:
"hits": [{"_index": "test_sort","_type": "zf","_id": "1","_score": null,"_source": {"id": 1,"name": "張三","age": 18,"shgx": [{"id": 1,"name": "老張","age": 50,"gx": "父親"},{"id": 2,"name": "張二","age": 22,"gx": "哥哥"}]},"sort": [50]},{"_index": "test_sort","_type": "zf","_id": "2","_score": null,"_source": {"id": 2,"name": "李四","age": 25,"shgx": [{"id": 3,"name": "李五","age": 23,"gx": "弟弟"}]},"sort": [23]}]非常重要!結(jié)果是錯(cuò)誤的。這是因?yàn)榍短孜臋n是作為主文檔的一部分返回的,在主查詢(xún)中的嵌套文檔的過(guò)濾條件并不能把不符合條件的內(nèi)部嵌套文檔過(guò)濾掉,返回的還是整個(gè)文檔(主文檔+完整的嵌套文檔)。以至于按嵌套文檔字段排序時(shí),還是按照全部的嵌套文檔進(jìn)行排序的。要正確的實(shí)現(xiàn)排序,就要把主查詢(xún)中有關(guān)嵌套文檔的查詢(xún)條件,在排序中再寫(xiě)一遍。
正確的寫(xiě)法:
POST /test_sort/zf/_search {"query": {"nested": {"path": "shgx","query": {"range": {"shgx.age": {"lt": 50}}}}},"sort": [{"shgx.age": {"nested_path": "shgx", "order": "desc","nested_filter": {"range": {"shgx.age": {"lt": 50}}}}}] }- 父子文檔-查父文檔-根據(jù)子文檔排序
構(gòu)造測(cè)試數(shù)據(jù),首先設(shè)置父子文檔的映射關(guān)系
然后,添加數(shù)據(jù)。
PUT /test_sort_2/zf_parent/1 {"id":1,"name":"張三","age":18 } PUT /test_sort_2/zf_parent/2 {"id":2,"name":"李四","age":25 }PUT /test_sort_2/shgx/1?parent=1 {"id":1,"name":"老張","age":50,"gx":"父親" }PUT /test_sort_2/shgx/2?parent=1 {"id":2,"name":"張二","age":22,"gx":"哥哥" }PUT /test_sort_2/shgx/3?parent=2 {"id":3,"name":"李五","age":23,"gx":"弟弟" }然后,根據(jù)age小于50歲的親屬排序,升序的話(huà)張三應(yīng)該是第一位。
POST /test_sort_3/zf_parent/_search {"query": {"has_child": {"type": "shgx","query": {"range": {"age": {"lt": 50}}},"inner_hits": {"name": "ZfShgx","sort": [{"age": {"order": "asc"}}]}}} }查看排序結(jié)果:
{"_index": "test_sort_3","_type": "zf_parent","_id": "2","_score": 1,"_source": {"id": 2,"name": "李四","age": 25},"inner_hits": {"ZfShgx": {"hits": {"total": 1,"max_score": null,"hits": [{"_type": "shgx","_id": "3","_score": null,"_routing": "2","_parent": "2","_source": {"id": 3,"name": "李五","age": 23,"gx": "弟弟"},"sort": [23]}]}}}},{"_index": "test_sort_3","_type": "zf_parent","_id": "1","_score": 1,"_source": {"id": 1,"name": "張三","age": 18},"inner_hits": {"ZfShgx": {"hits": {"total": 1,"max_score": null,"hits": [{"_type": "shgx","_id": "2","_score": null,"_routing": "1","_parent": "1","_source": {"id": 2,"name": "張二","age": 22,"gx": "哥哥"},"sort": [22]}]}}}}結(jié)果是錯(cuò)誤的,李四在前,查看官方文檔的父子文檔時(shí)不能直接用子文檔排序父文檔,或者用父文檔排序子文檔。那有沒(méi)有解決辦法呢?有一個(gè)曲線(xiàn)救國(guó)的方案,使用function_score通過(guò)子文檔的評(píng)分來(lái)影響父文檔的順序,但是評(píng)分算法很難做到精準(zhǔn)控制順序。
中文字符排序
在項(xiàng)目中發(fā)現(xiàn),中文字符在ES中的順序和在關(guān)系型數(shù)據(jù)庫(kù)中的順序不一致。經(jīng)查是因?yàn)镋S是用的unicode的字節(jié)碼做排序的。即先對(duì)字符(包括漢字)轉(zhuǎn)換成byte[]數(shù)組,然后對(duì)字節(jié)數(shù)組進(jìn)行排序。這種排序規(guī)則對(duì)ASIC碼(英文)是有效的,但對(duì)于中文等亞洲國(guó)家的字符不適用,怎么辦呢?有兩種解決辦法:
- 第一種,做拼音冗余。即在向ES同步數(shù)據(jù)時(shí)候,同步程序?qū)h字字段同時(shí)轉(zhuǎn)換成拼音,在ES里專(zhuān)門(mén)用于漢字排序。如:
- 第二種,使用ICU分詞插件。使用插件提供的icu_collation_keyword 映射類(lèi)型實(shí)現(xiàn)中文排序。
結(jié)論
- 嵌套文檔-根據(jù)主文檔字段排序時(shí),可以使用sort語(yǔ)句直接排序,無(wú)限制。
- 嵌套文檔-根據(jù)嵌套文檔字段排序時(shí),必須在sort子句里把所有嵌套相關(guān)的查詢(xún)條件,在sort里重新寫(xiě)一邊,排序才正確。
- 父子文檔-查父文檔根據(jù)子文檔排序時(shí),不能根據(jù)子文檔排序父文檔,反之亦然。
- 數(shù)據(jù)模型的復(fù)雜程度決定了排序的復(fù)雜程度,排序的復(fù)雜程度隨著模型的復(fù)雜程度成指數(shù)級(jí)增加。
- 中文字符可以通過(guò)做拼音冗余和使用ICU插件來(lái)實(shí)現(xiàn)排序。
轉(zhuǎn)載于:https://www.cnblogs.com/wangzhen3798/p/9884054.html
總結(jié)
以上是生活随笔為你收集整理的Elasticsearch中如何进行排序(中文+父子文档+嵌套文档)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Oracle学习笔记整理手册
- 下一篇: 结合jenkins以及PTP平台的性能回