Elasticsearch: nested對象


在處理大量數據時,關系數據庫存在很多問題。 無論是速度,高效處理,有效並行化,可擴展性還是成本,當數據量開始增長時,關系數據庫都會失敗。該關系數據庫的另一個挑戰是必須預先定義關系和模式。Elasticsearch也是一個NoSQL文檔數據存儲。 但是,盡管是一個NoSQL數據存儲,Elasticsearch在一定程度上提供了很多幫助來管理關系數據。 它支持類似SQL的連接,並且在嵌套和相關的數據實體上工作得非常棒。

比如,對於一個像下面的blog形式的文檔:

一個blog可能對應於很多的comments,或者一個員工對應於很多的經驗。這種數據就是關系數據。使用Elasticsearch,您可以通過保留輕松地工作與不同實體的關聯以及強大的全文搜索和分析。 Elasticsearch通過引入兩種類型的文檔關系模型使這成為可能:

  • nested 關系: 在這種關系中,這種一對多的關系存在於同一個文檔之中
  • parent-child 關系:在這種關系中,它們存在於不同的文檔之中。

這兩種關系在同一個模式下工作,即一對多個的關系。一個root或parent可以有一個及多個子object。

如上圖所示,在嵌套關系中,有一個根對象,它是我們擁有的主文檔,它包含一個稱為嵌套文檔的子文檔數組。 根對象內的文檔嵌套級別沒有限制。 例如,查看以下JSON以進行多級嵌套:

     {
         "location_id": "axdbyu",
         "location_name": "gurgaon",
         "company": [
           {
             "name": "honda",
             "modelName": [
               { "name": "honda cr-v", "price": "2 million" }
             ]
    }, {
             "name": "bmw",
             "modelName": [
               { "name": "BMW 3 Series", "price": "2 million"},
               { "name": "BMW 1 Series", "price": "3 million" }
             ]
      } ]
    }

下面,我們來做一個例子來展示一下為什么nested對象可以解決我們的問題。

Object數據類型

我們首先創建一個叫做developer的index,並輸入如下的兩個數據:

    POST developer/_doc/101
    {
      "name": "zhang san",
      "skills": [
        {
          "language": "ruby",
          "level": "expert"
        },
        {
          "language": "javascript",
          "level": "beginner"
        }
       ]
    }
     
    POST developer/_doc/102
    {
      "name": "li si",
      "skills": [
        {
          "language": "ruby",
          "level": "beginner"
        }
       ]
    }

上面顯示是一對多的一個index。

Object Query

這個時候我們想搜一個skills: language是ruby,並且level是biginner的文檔。我們可能想到的方法是:

    GET developer/_search
    {
      "query": {
        "bool": {
          "filter": [
            {
              "match": {
                "skills.language": "ruby"
              }
            },
            {
              "match": {
                "skills.level": "beginner"
              }
            }
          ]
        }
      }
    }

通過上面的搜尋,我們得到的結果是:

    {
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "max_score" : 0.0,
        "hits" : [
          {
            "_index" : "developer",
            "_type" : "_doc",
            "_id" : "101",
            "_score" : 0.0,
            "_source" : {
              "name" : "zhang san",
              "skills" : [
                {
                  "language" : "ruby",
                  "level" : "expert"
                },
                {
                  "language" : "javascript",
                  "level" : "beginner"
                }
              ]
            }
          },
          {
            "_index" : "developer",
            "_type" : "_doc",
            "_id" : "102",
            "_score" : 0.0,
            "_source" : {
              "name" : "li si",
              "skills" : [
                {
                  "language" : "ruby",
                  "level" : "beginner"
                }
              ]
            }
          }
        ]
      }
    }

我們可以看到,我們得到兩個結果。但是我們仔細查看一下發現得到的結果並不是我們想得到的。從我們的原意來說,我們想得到的是li si,因為只有li si這個人的language是ruby,並且他的level是biginner。zhang san這個文檔,應該不在搜尋之列。這是為什么呢?

原來,langauge及level是skills的JSON內部數組項。當JSON對象被Lucene扁平化后,我們失去了language和level之間的對應關系。取而代之的是如下的這種關系:

    {
      "name": "zhang san",
      "skills.language" :["ruby", "javascript"],
      "skills.level": ["expert", "beginner"]
    }

如上所示,我們看到的是一個扁平化的數組。之前的那種language和level之間的那種對應關系已經不存在了。

Object aggregation

同樣的問題也存在於aggregation中,比如我們想做一下的aggregation:

    GET developer/_search
    {
      "size": 0,
      "aggs": {
        "languages": {
          "terms": {
            "field": "skills.language.keyword"
          },
          "aggs": {
            "level": {
              "terms": {"field": "skills.level.keyword"}
            }
          }
        }
      }
    }

顯示的結果是:

    {
      "took" : 2,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "languages" : {
          "doc_count_error_upper_bound" : 0,
          "sum_other_doc_count" : 0,
          "buckets" : [
            {
              "key" : "ruby",
              "doc_count" : 2,
              "level" : {
                "doc_count_error_upper_bound" : 0,
                "sum_other_doc_count" : 0,
                "buckets" : [
                  {
                    "key" : "beginner",
                    "doc_count" : 2
                  },
                  {
                    "key" : "expert",
                    "doc_count" : 1
                  }
                ]
              }
            },
            {
              "key" : "javascript",
              "doc_count" : 1,
              "level" : {
                "doc_count_error_upper_bound" : 0,
                "sum_other_doc_count" : 0,
                "buckets" : [
                  {
                    "key" : "beginner",
                    "doc_count" : 1
                  },
                  {
                    "key" : "expert",
                    "doc_count" : 1
                  }
                ]
              }
            }
          ]
        }
      }
    }

顯然,對於key javascript來說,它並沒有expert對應的level,但是在我們的aggregation里顯示出來了。這個結果顯然是錯誤的。

nested 數據類型

nested數據類型能夠讓我們對object數組建立索引,並且分別進行查詢。

如果需要維護數組中每個對象的關系,請使用nested數據類型

為了能夠把我們的數據定義為nested,我們必須修改之前的索引mapping為:

    DELETE developer
     
    PUT developer
    {
      "mappings": {
        "properties": {
          "name": {
            "type": "text"
          },
          "skills": {
            "type": "nested",
            "properties": {
              "language": {
                "type": "keyword"
              },
              "level": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }

經過這樣的改造之后,重新把我們之前的數據輸入到index里:

    POST developer/_doc/101
    {
      "name": "zhang san",
      "skills": [
        {
          "language": "ruby",
          "level": "expert"
        },
        {
          "language": "javascript",
          "level": "beginner"
        }
       ]
    }
     
    POST developer/_doc/102
    {
      "name": "li si",
      "skills": [
        {
          "language": "ruby",
          "level": "beginner"
        }
       ]
    }

針對101,在Lucence中的數據結構變為:

    {
      "name": "zhang san",
      {
        "skills.language": "ruby",
        "skills.level": "expert"
      },
      {
        "skills.language": "javascript",
        "skills.level", "beginner"
      }
    }

nested query

我們來重新做我們之前的搜索:

    GET developer/_search
    {
      "query": {
        "nested": {
          "path": "skills",
          "query": {
            "bool": {
              "filter": [
                {
                  "match": {
                    "skills.language": "ruby"
                  }
                },
                {
                  "match": {
                    "skills.level": "beginner"
                  }
                }
              ]
            }
          }
        }
      }
    }

注意上面的“nested”字段。顯示的結果是:

    {
      "took" : 5,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 0.0,
        "hits" : [
          {
            "_index" : "developer",
            "_type" : "_doc",
            "_id" : "102",
            "_score" : 0.0,
            "_source" : {
              "name" : "li si",
              "skills" : [
                {
                  "language" : "ruby",
                  "level" : "beginner"
                }
              ]
            }
          }
        ]
      }
    }

顯然,我們只得到了一個我們想要的結果。

nested aggregation

同樣,我們可以對我們的index來做一個aggregation:

    GET developer/_search
    {
      "size": 0,
      "aggs": {
        "nested_skills": {
          "nested": {
            "path": "skills"
          },
          "aggs": {
            "languages": {
              "terms": {
                "field": "skills.language"
              },
              "aggs": {
                "levels": {
                  "terms": {
                    "field": "skills.level"
                  }
                }
              }
            }
          }
        }
      }
    }

顯示的結果是:

    {
      "took" : 1,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "nested_skills" : {
          "doc_count" : 3,
          "languages" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ruby",
                "doc_count" : 2,
                "levels" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : "beginner",
                      "doc_count" : 1
                    },
                    {
                      "key" : "expert",
                      "doc_count" : 1
                    }
                  ]
                }
              },
              {
                "key" : "javascript",
                "doc_count" : 1,
                "levels" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : "beginner",
                      "doc_count" : 1
                    }
                  ]
                }
              }
            ]
          }
        }
      }
    }

從上面顯示的結果,可以看出來對於ruby來說,它分別對應於一個bigginer及一個expert。這個和我們之前的數據是一樣的。對於javascript來說,它只有一個beginner的level。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM