映射和分析
數據類型差異
映射(mapping)機制用於進行字段類型確認,將每個字段匹配爲一種確定的數據類型(string, number, booleans, date等)。
分析(analysis)機制用於進行全文文本(Full Text)的分詞,以建立供搜索用的反向索引。
通過下面的請求。查看document的mapping關係:
GET /gb/_mapping/tweet
在Elasticsearch中核心數據類型(strings, numbers, booleans及dates)以不同的方式進行索引。
但是更大的區別在於確切值(exact values)(比如string類型)及全文文本(full text)之間。
這兩者的區別才真的很重要 - 這是區分搜索引擎和其他數據庫的根本差異。
確切值對決全文文本
確切值(Exact values) vs. 全文文本(Full text)
Elasticsearch中的數據可以大致分爲兩種類型:確切值 及 全文文本。
確切值是確定的,正如它的名字一樣。比如一個date或用戶ID,也可以包含更多的字符串比如username或email地址。
確切值”Foo”和”foo”就並不相同。確切值2014和2014-09-15也不相同。
全文文本,從另一個角度來說是文本化的數據(常常以人類的語言書寫),比如一篇推文(Twitter的文章)或郵件正文。
爲了方便在全文文本字段中進行這些類型的查詢,Elasticsearch首先對文本分析(analyzes),然後使用結果建立一個倒排索引。
倒排索引
倒排索引
Elasticsearch使用一種叫做倒排索引(inverted index)的結構來做快速的全文搜索。倒排索引由在文檔中出現的唯一的單詞列表,以及對於每個單詞在文檔中的位置組成。
例如,我們有兩個文檔,每個文檔content字段包含:
The quick brown fox jumped over the lazy dog
Quick brown foxes leap over lazy dogs in summer
爲了創建倒排索引,我們首先切分每個文檔的content字段爲單獨的單詞(我們把它們叫做詞(terms)或者表徵(tokens)),把所有的唯一詞放入列表並排序,結果是這個樣子的:
Term | Doc_1 | Doc_2 |
---|---|---|
Quick | X | |
The | X | |
brown | X | X |
dog | X | |
dogs | X | |
fox | X | |
foxes | X | |
in | X | |
jumped | X | |
lazy | X | X |
leap | X | |
over | X | X |
quick | X | |
summer | X | |
the | X |
現在,如果我們想搜索”quick brown”,我們只需要找到每個詞在哪個文檔中出現即可:
Term | Doc_1 | Doc_2 |
---|---|---|
brown | X | X |
quick | X | |
Total | 2 | 1 |
兩個文檔都匹配,但是第一個比第二個有更多的匹配項。 如果我們加入簡單的相似度算法(similarity algorithm),計算匹配單詞的數目,這樣我們就可以說第一個文檔比第二個匹配度更高——對於我們的查詢具有更多相關性。
IMPORTANT
這很重要。你只可以找到確實存在於索引中的詞,所以索引文本和查詢字符串都要標準化爲相同的形式。
這個標記化和標準化的過程叫做分詞(analysis),這個在下節中我們討論。
分析
分析和分析器
分析(analysis)是這樣一個過程:
- 首先,標記化一個文本塊爲適用於倒排索引單獨的詞(term)
- 然後標準化這些詞爲標準形式,提高它們的“可搜索性”或“查全率”
這個工作是分析器(analyzer)完成的。一個分析器(analyzer)只是一個包裝用於將三個功能放到一個包裏:
字符過濾器
首先字符串經過字符過濾器(character filter),它們的工作是在標記化前處理字符串。字符過濾器能夠去除HTML標記,或者轉換”&”爲”and”。
分詞器
下一步,分詞器(tokenizer)被標記化成獨立的詞。一個簡單的分詞器(tokenizer)可以根據空格或逗號將單詞分開。
標記過濾
最後,每個詞都通過所有標記過濾(token filters),它可以修改詞(例如將”Quick”轉爲小寫),去掉詞(例如停用詞像”a”、”and”、”the”等等),或者增加詞(例如同義詞像”jump”和”leap”)
Elasticsearch提供很多開箱即用的字符過濾器,分詞器和標記過濾器。這些可以組合來創建自定義的分析器以應對不同的需求。
內建的分析器
Elasticsearch還附帶了一些預裝的分析器,可以直接使用它們。下面來演示這個字符串分詞後的表現差異:
"Set the shape to semi-transparent by calling set_trans(5)"
標準分析器
標準分析器是Elasticsearch默認使用的分析器。對於文本分析,它對於任何語言都是最佳選擇。它根據Unicode Consortium的定義的單詞邊界(word boundaries)來切分文本,然後去掉大部分標點符號。最後,把所有詞轉爲小寫。產生的結果爲:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
簡單分析器
簡單分析器將非單個字母的文本切分,然後把每個詞轉爲小寫。產生的結果爲:
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器依據空格切分文本。它不轉換小寫。產生結果爲:
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
語言分析器
特定語言分析器適用於很多語言。它們能夠考慮到特定語言的特性。例如,english分析器自帶一套英語停用詞庫——像and或the這些與語義無關的通用詞。這些詞被移除後,因爲語法規則的存在,英語單詞的主體含義依舊能被理解。
english分析器將會產生以下結果:
set, shape, semi, transpar, call, set_tran, 5
當分析器被使用
當我們索引(index)一個文檔,全文字段會被分析爲單獨的詞來創建倒排索引。不過,當我們在全文字段搜索(search)時,我們要讓查詢字符串經過同樣的分析流程處理,以確保這些詞在索引中存在。
全文查詢我們將在稍後討論,理解每個字段是如何定義的,這樣纔可以讓它們做正確的事:
- 當你查詢全文(full text)字段,查詢將使用相同的分析器來分析查詢字符串,以產生正確的詞列表。
- 當你查詢一個確切值(exact value)字段,查詢將不分析查詢字符串,但是你可以自己指定。
現在可以解釋《映射和分析》的開頭會產生那種結果:
- date字段包含一個確切值:單獨的一個詞”2014-09-15”。
- _all字段是一個全文字段,所以分析過程將日期轉爲三個詞:”2014”、”09”和”15”。
當我們在_all字段查詢2014,它一個匹配到12條推文,因爲這些推文都包含詞2014:
GET /_search?q=2014 # 12 results
當我們在_all字段中查詢2014-09-15,首先分析查詢字符串,產生匹配任一詞2014、09或15的查詢語句,它依舊匹配12個推文,因爲它們都包含詞2014。
GET /_search?q=2014-09-15 # 12 results !
當我們在date字段中查詢2014-09-15,它查詢一個確切的日期,然後只找到一條推文:
GET /_search?q=date:2014-09-15 # 1 result
當我們在date字段中查詢2014,沒有找到文檔,因爲沒有文檔包含那個確切的日期:
GET /_search?q=date:2014 # 0 results !
測試分析器
尤其當你是Elasticsearch新手時,對於如何分詞以及存儲到索引中理解起來比較困難。爲了更好的理解如何進行,你可以使用analyze API來查看文本是如何被分析的。在查詢字符串參數中指定要使用的分析器,被分析的文本做爲請求體:
GET /_analyze?analyzer=standard&text=Text to analyze
token是一個實際被存儲在索引中的詞。position指明詞在原文本中是第幾個出現的。start_offset
和end_offset
表示詞在原文本中佔據的位置。
指定分析器
當Elasticsearch在你的文檔中探測到一個新的字符串字段,它將自動設置它爲全文string字段並用standard分析器分析。
你不可能總是想要這樣做。也許你想使用一個更適合這個數據的語言分析器。或者,你只想把字符串字段當作一個普通的字段——不做任何分析,只存儲確切值,就像字符串類型的用戶ID或者內部狀態字段或者標籤。
爲了達到這種效果,我們必須通過映射(mapping)人工設置這些字段。
可以參考:elasticsearch 2.3.4 java API 連接,ik分詞器,設置集羣節點,創建index,mapping的幾種方式
映射
爲了能夠把日期字段處理成日期,把數字字段處理成數字,把字符串字段處理成全文本(Full-text)或精確的字符串值,Elasticsearch需要知道每個字段裏面都包含了什麼類型。這些類型和字段的信息存儲(包含)在映射(mapping)中。
核心簡單字段類型
類型 | 表示的數據類型 |
---|---|
String | string |
Whole number | byte, short, integer, long |
Floating point | float, double |
Boolean | boolean |
Date | date |
注意
這意味着,如果你索引一個帶引號的數字——”123”,它將被映射爲”string”類型,而不是”long”類型。然而,如果字段已經被映射爲”long”類型,Elasticsearch將嘗試轉換字符串爲long,並在轉換失敗時會拋出異常。
查看映射
我們可以使用_mapping後綴來查看Elasticsearch中的映射。在本章開始我們已經找到索引gb類型tweet中的映射:GET /gb/_mapping/tweet
自定義字段映射
雖然大多數情況下基本數據類型已經能夠滿足,但你也會經常需要自定義一些特殊類型(fields),特別是字符串字段類型。 自定義類型可以使你完成以下幾點:
- 區分全文(full text)字符串字段和準確字符串字段(譯者注:就是分詞與不分詞,全文的一般要分詞,準確的就不需要分詞,比如『中國』這個詞。全文會分成『中』和『國』,但作爲一個國家標識的時候我們是不需要分詞的,所以它就應該是一個準確的字符串字段)。
- 使用特定語言的分析器(譯者注:例如中文、英文、阿拉伯語,不同文字的斷字、斷詞方式的差異)
- 優化部分匹配字段
- 指定自定義日期格式(譯者注:這個比較好理解,例如英文的 Feb,12,2016 和 中文的 2016年2月12日)
- 以及更多
string類型的字段,默認的,考慮到包含全文本,它們的值在索引前要經過分析器分析,並且在全文搜索此字段前要把查詢語句做分析處理。
對於string字段,兩個最重要的映射參數是index和analyer。
index
index參數控制字符串以何種方式被索引。它包含以下三個值當中的一個:
值 | 解釋 |
---|---|
analyzed | 首先分析這個字符串,然後索引。換言之,以全文形式索引此字段。 |
not_analyzed | 索引這個字段,使之可以被搜索,但是索引內容和指定值一樣。不分析此字段。 |
no | 不索引這個字段。這個字段不能爲搜索到 |
string類型字段默認值是analyzed。如果我們想映射字段爲確切值,我們需要設置它爲
not_analyzed:
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
其他簡單類型(long、double、date等等)也接受index參數,但相應的值只能是no和not_analyzed,它們的值不能被分析。
分析
對於analyzed類型的字符串字段,使用analyzer參數來指定哪一種分析器將在搜索和索引的時候使用。默認的,Elasticsearch使用standard分析器,但是你可以通過指定一個內建的分析器來更改它,例如whitespace、simple或english。
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
更新映射
你可以在第一次創建索引的時候指定映射的類型。此外,你也可以晚些時候爲新類型添加映射(或者爲已有的類型更新映射)。
重要
你可以向已有映射中增加字段,但你不能修改它。如果一個字段在映射中已經存在,這可能意味着那個字段的數據已經被索引。如果你改變了字段映射,那已經被索引的數據將錯誤並且不能被正確的搜索到。
我們可以更新一個映射來增加一個新字段,但是不能把已有字段的類型那個從analyzed改到not_analyzed。
爲了演示兩個指定的映射方法,讓我們首先刪除索引gb:
DELETE /gb
然後創建一個新索引,指定tweet字段的分析器爲english:
PUT /gb <1>
{
"mappings": {
"tweet" : {
"properties" : {
"tweet" : {
"type" : "string",
"analyzer": "english"
},
"date" : {
"type" : "date"
},
"name" : {
"type" : "string"
},
"user_id" : {
"type" : "long"
}
}
}
}
}
<1> 這將創建包含mappings的索引,映射在請求體中指定。
再後來,我們決定在tweet的映射中增加一個新的not_analyzed類型的文本字段,叫做tag,使用_mapping後綴:
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
注意到我們不再需要列出所有的已經存在的字段,因爲我們沒法修改他們。我們的新字段已經被合併至存在的那個映射中。
複合核心字段類型
除了之前提到的簡單的標量類型,JSON還有null值,數組和對象,所有這些Elasticsearch都支持:
多值字段
我們想讓tag字段包含多個字段,這非常有可能發生。我們可以索引一個標籤數組來代替單一字符串:
{ "tag": [ "search", "nosql" ]}
對於數組不需要特殊的映射。任何一個字段可以包含零個、一個或多個值,同樣對於全文字段將被分析併產生多個詞。
言外之意,這意味着數組中所有值必須爲同一類型。你不能把日期和字符竄混合。如果你創建一個新字段,這個字段索引了一個數組,Elasticsearch將使用第一個值的類型來確定這個新字段的類型。
當你從Elasticsearch中取回一個文檔,任何一個數組的順序和你索引它們的順序一致。你取回的_source字段的順序同樣與索引它們的順序相同。
然而,數組是做爲多值字段被索引的,它們沒有順序。在搜索階段你不能指定“第一個值”或者“最後一個值”。倒不如把數組當作一個值集合(bag of values)。
空字段
當然數組可以是空的。這等價於有零個值。事實上,Lucene沒法存放null值,所以一個null值的字段被認爲是空字段。
這四個字段將被識別爲空字段而不被索引:
"empty_string": "",
"null_value": null,
"empty_array": [],
"array_with_null_value": [ null ]
多層對象
我們需要討論的最後一個自然JSON數據類型是對象(object)——在其它語言中叫做hash、hashmap、dictionary 或者 associative array.
內部對象(inner objects)經常用於在另一個對象中嵌入一個實體或對象。例如,做爲在tweet文檔中user_name和user_id的替代,我們可以這樣寫:
{
"tweet": "Elasticsearch is very flexible",
"user": {
"id": "@johnsmith",
"gender": "male",
"age": 26,
"name": {
"full": "John Smith",
"first": "John",
"last": "Smith"
}
}
}
內部對象的映射
Elasticsearch 會動態的檢測新對象的字段,並且映射它們爲 object 類型,將每個字段加到 properties 字段下
{
"gb": {
"tweet": { <1>
"properties": {
"tweet": { "type": "string" },
"user": { <2>
"type": "object",
"properties": {
"id": { "type": "string" },
"gender": { "type": "string" },
"age": { "type": "long" },
"name": { <3>
"type": "object",
"properties": {
"full": { "type": "string" },
"first": { "type": "string" },
"last": { "type": "string" }
}
}
}
}
}
}
}
}
<1> 根對象.
<2><3> 內部對象.
對user和name字段的映射與tweet類型自己很相似。事實上,type映射只是object映射的一種特殊類型,我們將 object 稱爲根對象。它與其他對象一模一樣,除非它有一些特殊的頂層字段,比如 _source, _all 等等。
內部對象是怎樣被索引的
Lucene 並不瞭解內部對象。 一個 Lucene 文件包含一個鍵-值對應的扁平表單。 爲了讓 Elasticsearch 可以有效的索引內部對象,將文件轉換爲以下格式:
{
"tweet": [elasticsearch, flexible, very],
"user.id": [@johnsmith],
"user.gender": [male],
"user.age": [26],
"user.name.full": [john, smith],
"user.name.first": [john],
"user.name.last": [smith]
}
內部欄位可被歸類至name,例如”first”。 爲了區別兩個擁有相同名字的欄位,我們可以使用完整路徑,例如”user.name.first” 或甚至類型名稱加上路徑:”tweet.user.name.first”。
注意: 在以上扁平化文件中,並沒有欄位叫作user也沒有欄位叫作user.name。 Lucene 只索引階層或簡單的值,而不會索引複雜的資料結構。
對象-數組
內部對象數組
最後,一個包含內部對象的數組如何索引。 我們有個數組如下所示:
{
"followers": [
{ "age": 35, "name": "Mary White"},
{ "age": 26, "name": "Alex Jones"},
{ "age": 19, "name": "Lisa Smith"}
]
}
此文件會如我們以上所說的被扁平化,但其結果會像如此:
{
"followers.age": [19, 26, 35],
"followers.name": [alex, jones, lisa, smith, mary, white]
}
{age: 35}與{name: Mary White}之間的關聯會消失,因每個多值的欄位會變成一個值集合,而非有序的陣列。 這讓我們可以知道:
是否有26歲的追隨者?
但我們無法取得準確的資料如:
是否有26歲的追隨者且名字叫Alex Jones?
關聯內部對象可解決此類問題,我們稱之爲嵌套對象,我們之後會在嵌套對象中提到它。
參考資料:
Elasticsearch權威指南
備註:
轉載請註明出處:http://blog.csdn.net/wsyw126/article/details/71079841
作者:WSYW126