相关文章推荐

由於在 ES 中,所有單個文檔的增刪改都是原子性的操作,因此將相關的實體數據都儲存在同一個文檔是很好的,且由於所有信息都在一個文檔中,因此當我們查詢時就沒有必要像 mysql 一樣去關聯很多張表,只要搜一遍文檔就可以查出所有需要的數據,查詢效率非常高

因此除了基本數據類型之外,ES 也支持使用複雜的數據類型,像是數組、內部對象,而要使用內部對象的話,需要使用 nested 來定義索引,使文檔內可以包含一個內部對象

為什麼不用 object 而要使用 nested 來定義索引的原因是,object 類型會使得內部對象的關聯性丟失

這是因為 Lucene 底層其實沒有內部對象的概念,所以 ES 會利用簡單的列表儲存字段名和值,將 object 類型的對象層次攤平,再傳給 Lucene

假設 user 類型是 object,當插入一筆新的數據時,ES 會將他轉換為下面的內部文檔,其中可以看見 alice 和 white 的關聯性丟失了

PUT mytest/doc/1
    "group": "fans",
    "user": [
        { "first": "John", "last": "Smith" },
        { "first": "Alice", "last": "White" }
轉換後的內部文檔
    "group": "fans",
    "user.first": [ "alice", "john" ],
    "user.last": [ "smith", "white" ]

理論上從插入的數據來看,應該搜索 “first 為 Alice 且 last 為 White” 時,這個文檔才算符合條件被搜出來,其他的條件都不算符合,但是因為 ES 把 object 類型的對象攤平了,所以實際上如果搜索 “first 為 Alice 且 last 為 Smith”,這個文檔也會當作符合的文檔被搜出來,但這樣就違反我們的意願了,我們希望內部對象自己的關聯性還是存在的

因此在使用內部對象時,要改使用 nested 類型來取代 object 類型 (因為 nested 類型不會被攤平,下面說明)

定義一個 nested 類型的 mapping,user 是一個內部對象,裡面包含了 first、last 和 age,因為 user 設置了 nested 類型,因此 user 對象會被索引在獨立的嵌套文檔中

PUT mytest
    "mappings": {
    	"doc": {
        	"properties": {
                //group是正常的keyword字段
                "group": { "type": "keyword" },
                //user是一個nested類型,表示他底下還會包含子對象
            	"user": {
                	"type": "nested",
                    "properties": {
                    	"first": {
                            "type": "keyword"
                        "last": {
                            "type": "keyword"
                        "age": {
                            "type": "integer"

插入兩筆數據

POST mytest/doc
    "group": "fans",
    "user": {
        "first": "Taylor",
        "last": "Swift",
        "age": 30
POST mytest/doc
    "group": "fans",
    "user": [
            "first": "Amy",
            "last": "White",
            "age": 18
            "first": "John",
            "last": "Smith",
            "age": 22

因此在ES中存在的文檔如下

"hits": [
        "_source": {
            "group": "fans",
            "user": {
                "first": "Taylor",
                "last": "Swift",
                "age": 30
        "_source": {
            "group": "fans",
            "user": [
                    "first": "Amy",
                    "last": "White",
                    "age": 18
                    "first": "John",
                    "last": "Smith",
                    "age": 22

由於嵌套對象被索引在獨立的隱藏文檔中,因此我們無法直接使用一般的 query 去查詢他,我們必須改使用 “nested查詢” 去查詢他們

  • nested 查詢是一個葉子子句,因此外層需要使用 query 或是 bool 來包含他,且因為 nested 查詢是一個葉子子句,所以他也可以像一般的葉子子句一樣被 bool 層層嵌套
  • nested 查詢的內部必須要包含一個 path 參數,負責指定要用的是哪個 nested 類型的字段,且要包含一個 query,負責進行此嵌套對象內的查詢
  • 如果是將 bool 子句寫在 nested 裡面,表示要查詢某個子對象中,必須同時包含這兩個條件

    也就是說,下面的查詢實際上是查找必須存在一個子對象,然後此對象必須同時滿足 user.first 為 Taylor,且 user.last 為 Swift,所以能夠找到一個存在的文檔

    GET mytest/doc/_search
        "query": {
            "nested": {
            	"path": "user",
                "query": {
                    "bool": {
                    	"must": [
                            { "term": { "user.first": "Taylor" } },
                            { "term": { "user.last": "Swift" } }
    
    "hits": [
            "_source": {
                "group": "fans",
                "user": {
                    "first": "Taylor",
                    "last": "Swift",
                    "age": 30
    

    但將查詢改寫成下面這樣的話,則搜索不到任何結果,因為沒有一個子對象同時滿足 user.first 為 Taylor 且 user.last 為xxx

    GET mytest/doc/_search
        "query": {
            "nested": {
            	"path": "user",
                "query": {
                    "bool": {
                    	"must": [
                            { "term": { "user.first": "Taylor" } },
                            { "term": { "user.last": "xxx" } }
    

    下面的查詢表示只要 user 中包含 user.first 為 Amy,且也包含 user.last 為 Smith 就行了,但不是要求一定要有一個子對象是 user.first 為 Amy 且他的 user.last 剛好也是 Smith,所以結果才能搜出一個文檔

    如果這裡將 bool 改寫在 nested 裡面,那就搜不出任何文檔了

    GET mytest/doc/_search
        "query": {
            "bool": {
                "filter": [
                        "nested": {
                            "path": "user",
                            "query": {
                                "term": {
                                    "user.first": "Amy"
                        "nested": {
                            "path": "user",
                            "query": {
                                "term": {
                                    "user.last": "Smith"
    
    "hits": [
            "_source": {
                "group": "fans",
                "user": [
                        "first": "Amy",
                        "last": "White",
                        "age": 18
                        "first": "John",
                        "last": "Smith",
                        "age": 22
    

    當查詢 “user.first = July 或 user.last = Month” 時,第一個嵌套文檔的分數最高,第二個嵌套文檔次之,第三個嵌套文檔的分數最低

    "hits": [
            "_source": {
                "group": "fans",
                "user": [
                    { "first": "July", "last": "Month", "age": 18 },
                    { "first": "Aug", "last": "Month", "age": 22 },
                    { "first": "Monday", "last": "Day", "age": 25 }
    

    不過要注意,如果 nested 查詢放在一個 filter 子句中,就算定義了 score_mode 也不會生效,因為 filter 不打分,所以 score_mode 就沒有任何意義

    GET mytest/doc/_search
        "query": {
            "nested": {
            	"path": "user",
                "score_mode": "max", //返回最佳匹配嵌套文檔的_score給根文檔使用
                "query": {
                    "bool": {
                    	"should": [
                            { "term": { "user.first": "July" } },
                            { "term": { "user.last": "Month" } }
    
  • 儘管嵌套對象儲存於獨立的隱藏文檔中,但依然有方法按照嵌套字段的值排序
  • 假設我們想要查出 user.first 為 Amy,且依照 user.age 這個內部對象的字段,由小到大進行排序,查詢語句如下
    GET mytest/doc/_search
        "query": {
            "nested": {
                "path": "user",
                "query": {
                    "term": {
                        "user.first": "Amy"
        "sort": {
            "user.age": {
                "nested": {
                    "path": "user"
                "order": "asc"
    
  •  
    推荐文章