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