elasticsearch-對映和分析
elasticsearch-對映和分析
對映和分析
- 檢視索引對映分析
/gb/_mapping
- 全文搜尋和精確欄位搜尋
/gb/_search?q=2014-09-15
/gb/_search?q=date:2014-09-15
精確欄位搜尋和全文搜尋是搜尋引擎和其它資料庫的本質區別
精確值VS全文
- es中的資料可以概括的分為兩類:精確值和全文
- 精確值:例如日期或者使用者id, 字串也可以表示精確值,例如使用者名稱或者郵箱地址,
對於精確值來說,Foo和foo是不同的,2014和2014-09-15也是不同的 - 全文:是指文字資料,例如一個推文的內容或者一封郵件的內容
全文通常是指非結構化的資料 - 精確值很容易查詢,一般用sql就可以直接查,但是查詢全文資料要微妙的多
我們問的不只是這個文件匹配查詢嗎,而是該文件匹配程度有多大,換句話說,該文件與給出的查詢相關性如何 - 我們很少對全文型別的域做精確匹配,相反,我們希望在文字型別的域中做搜尋,不僅如此,
我們還希望搜尋能夠理解我們的意圖- 搜尋 UK ,會返回包含 United Kindom 的文件。
- 搜尋 jump ,會匹配 jumped , jumps , jumping ,甚至是 leap 。
- 搜尋 johnny walker 會匹配 Johnnie Walker , johnnie depp 應該匹配 Johnny Depp 。
- fox news hunting 應該返回福克斯新聞( Foxs News )中關於狩獵的故事,同時, fox hunting news 應該返回關於獵狐的故事。
為了促進這類在全文域中的查詢,es首先分析文件,然後根據結果建立倒排索引,接下來討論倒排索引和分析過程
倒排索引
- es使用一種稱為 倒排索引 的結構,它適用於快速全文搜尋,一個倒排索引由文件中所有不重複詞的列表組成
對於其中的每個詞,有一個包含它的文件列表 - 假設我們有兩個文件,每個文件的content域內容如下
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
為了建立倒排索引我們需要將每個文件的content域拆分成單獨的詞(我們稱之為詞條或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
兩個文件都匹配,但是第一個比第二個匹配度更高,如果我們僅適用詞條匹配數量相似性演算法
第一個文件比第二個文件更佳
但是也有一些問題,
Quick 和 quick 以獨立的詞條出現,然而使用者可能認為它們是相同的詞。
fox 和 foxes 非常相似, 就像 dog 和 dogs ;他們有相同的詞根。
jumped 和 leap, 儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。
3. 文件中多個欄位and查詢
/gb,us/_search
{
"query": {
"bool": {
"must": [
{"match_phrase": {"name": "mary"}},
{"match_phrase": {"tweet": "however"}}
]
}
}
}
- 這非常重要,你只能搜尋在索引中出現的詞條,所以索引文字和查詢字串必須標準化為相同的格式
分析和分析器
- 分析包含下面的過程
首先,將一塊文字分成適合於倒排索引的獨立的詞條
之後,將這些詞條統一化為標準格式提高它們的可搜尋性,或者recall - 分析器執行上面的工作實際上是將三個功能包在了一起
- 字元過濾器
首先字串按順序通過每個字元過濾器,它們的任務是在分詞前整理字串,一個字元過濾器可以用來去掉HTML,
或者將&轉化為and - 分詞器
字串被分詞器分為單個的詞條,一個簡單的分詞器在遇到空格和標點的時候,可能會將文字拆分成詞條 - token過濾器
詞條按照順序通過每個token過濾器,這個過程可能會改變詞條(大寫轉小寫),
刪除詞條(例如像a and the等無用詞),或者增加詞條(向jump和leap這種同義詞)
- es提供了開箱即用的字元過濾器、分詞器、token過濾器,這些可以組合起來形成自定義的分析器,用於不同的目的,
-
內建分析器
es還提供了可以直接使用的預包裝的分析器,接下來看看最重要的分析器,為了證明它們的差異
我們看看每個分析器會從下面的字串得到哪些詞條Set the shape to semi-transparent by calling set_trans(5)
3.1 標準分析器
標準分析器是es預設使用的分析器,它是分析各種語言文字最常用的選擇,他根據unicode聯盟定義的單詞邊界劃分文字
刪除絕大部分標點,最後將詞條小寫,它會產生set, the, shape, to, semi, transparent, by, calling, set_trans, 5
3.2 簡單分析器
簡單分析器是在任何不是字母的地方分割文字,它會產生set, the, shape, to, semi, transparent, by, calling, set, trans
3.3 空格分析器
空格分析器在空格的地方劃分文字,它會產生Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
3.4 語言分析器
特定語言分析器可用於很多語言,它們可以考慮指定語言的特點,英語分析器附帶了一組英語無用詞,它們會直接被刪除
由於該分析器理解英語語法的規則,這個分詞器可以提取英語單詞的詞幹
英語分詞器會產生下面的詞條set, shape, semi, transpar, call, set_tran, 5
注意看 transparent、 calling 和 set_trans 已經變為詞根格式。 -
什麼時候使用分析器
當我們索引一個文件時,它的全文域被分析成詞條用來建立倒排索引,但是,當我們在全文域進行搜尋的時候
也需要將查詢字串通過相同的分析過程,以保證我們查詢的詞條格式和索引中的詞條格式一致
- 當你查詢一個全文域時,會對查詢字串應用相同的分析器,以產生正確的搜尋詞條列表
- 當你查詢一個精確值域時,不會分析查詢字串,而是搜尋你指定的精確值
再看之前的問題,date是精確值域:單獨的詞條,2014-05-19,所以GET /_search?q=date:2014是查不到的
- 測試分析器
有時候很難理解分詞的過程和儲存到索引中的詞條,為了理解發生了什麼
我們可以使用analyze API來看文字是如何被拆分的,在訊息體裡,指定分析器和要分析的文字
GET /_analyze
{
"analyzer": "standard",
"text": "Text to analyze"
}
結果中,每個元素代表一個單獨的詞條
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 2
}
]
}
token是實際儲存到索引中的詞條,position指明詞條在原始文字中出現的位置,
start_offset和end_offset指明詞條在原始字串中的位置
每個分析器的type值都不一樣,可以忽略它們,它們在Elasticsearch中的唯一作用在於keep_types token 過濾器
- analyze API是一個有用的工具,它有助於我們理解es索引內部發生了什麼,後面會進一步討論
- 指定分析器
當es在你的文件中檢測到一個新的字串域,它會自動設定其為一個全文字串域,使用標準分析器對它進行分析
當我們不希望總是這樣,希望使用一個不同的分析器,適用於我們的資料使用的語言,有時候你想一個字串域
就是一個字串域,不使用分析,直接索引你傳入的精確值,例如使用者Id或者一個內部的狀態域或標籤
要做到這一點我們就必須手動指定這些域的對映
對映
- 為了將時間域視為時間,數字域視為數字,字串域視為全文或精確值字串,es需要知道每個域中的資料型別
這個資訊包含在對映中
索引中每個文件都有型別,每種型別都有它自己的對映, - 核心簡單域型別
- es支援如下簡單域型別
- 字串 text
- 整數 byte、short、integer、long
- 浮點數 float double
- 布林型 boolean
- 日期 date
-
當你索引一個包含新域的文件--之前未曾出現,es會使用動態對映,
通過json中節本資料型別,嘗試猜測域型別
這意味著如果你想通過"13"索引一個數字,它會被對映為text型別,而不是long,
但是如果這個域已經被對映為long,那麼es會嘗試將該字串轉換為long型別,如果無法轉換,則丟擲一個異常 -
檢視對映
通過 /索引/_mapping,我們可以檢視es中一個或多個索引的對映
es根據我們索引的文件,為域(稱為屬性)動態生成的對映
- 錯誤的對映,例如將age域對映為string型別,而不是integer或long,會導致查詢出現令人困惑的結果,
- 自定義域對映
儘管很多情況下基本域資料型別已經夠用,但你經常需要為單獨域自定義對映,特別是字串域
自定義對映允許你執行下面的操作
- 全文字串域與精確值字串域的區別
- 使用特定語言分析器
- 優化域以適應部分匹配
- 指定自定義資料格式
- 。。。
域最重要的屬性是type,對於不是text的域,你一般只需要設定type
預設text型別域會被認為包含全文,也就是說它們的值在索引前,會通過一個分析器,
針對於這個域的查詢在搜尋前也會經過這個分析器
增加一個tag2域,index選擇為false
{
"properties": {
"tag2": {
"type": "text",
"index": false
}
}
}
也就是說tag2的域是不能被索引的查詢的
- text域對映的兩個最重要屬性index、analyzer
- index屬性控制怎樣索引字串: true、false
true:索引這個域,false:不索引這個域
text域的index屬性預設值是true,如果我們想不索引這個域,我們可以設定它為false - analyzer
對於analyzer屬性指定在搜尋時和索引時使用的分析器,es預設使用standard分析器,
但是也可以指定一個內建的分析器替代它:simple、whitespace、english
- 更新對映
- 建立索引,指定域的型別和tweet域使用的分析器
PUT /gb
{
"mappings": {
"properties": {
"tweet": {
"type": "text",
"analyzer": "english"
},
"date": {
"type": "date"
},
"name": {
"type": "text"
},
"user_id": {
"type": "long"
}
}
}
}
通過訊息體中指定的mappings建立了索引,
稍後我們在gb索引中增加一個新名為tag的type型別為keyword不分詞文字域,使用_mapping
PUT /gb/_mapping
{
"properties": {
"tag": {
"type": "keyword"
}
}
}
注意:我們不需要再次列出所有已經存在的域,因為無論如何我們都無法改變它們,
新域已經被合併到了存在的對映中
- 測試對映
GET /gb/_analyze
{
"field": "name",
"text": "a b c "
}
{
"field": "tag",
"text": "a b c "
}
訊息體裡傳輸我們想要分析的文字
name域產生三個詞條a、b、c,tag域產生一個詞條“a b c ”
換句話說,我們的對映正常工作
- 分析器使用whitespace的對映情況
GET /test01/_analyze
{
"field": "tag3",
"text": "abc-def aa ddef"
}
會分析對映成三個token, abc-def aa ddef
複雜核心域型別
- 除了我們提提到的簡單標量資料型別,json還有null值、陣列和物件,這些es都支援
- 多值域
- 很有可能,我們希望tag域包含多個標籤,我們可以以陣列的形式索引標籤
{ "tag": [ "search", "nosql" ]}
對於陣列沒有特殊的對映需求,任何域都可以包含0、1或者多個值,就像全文域分析得到多個詞條
這暗示,陣列中的所有值都必須是相同的資料型別,不能將日期和字串混在一起,
如果通過索引陣列來建立新的域,es會用陣列中第一個值的資料型別作為這個域的型別
注意:當你從es得到一個文件,每個陣列的順序和你當初索引文件時一樣,你得到的_source域
和當初索引文件時一模一樣的json - 但是陣列是以多值域索引的-可以搜尋,但是無序的,在搜尋的時候,不能指定第一個或者最後一個
更確切的說,把陣列想象成裝在袋子裡的值
- 空域
- 當然陣列可以為空,這相當於存在零值,事實上,在lucene中不能儲存null值,
所以我們認為存null值的域為空域,下面3種域被認為是空域,將不會被索引
{
"null_value": null,
"empty_array": [],
"array_with_null_value": [null]
}
- 多層級物件
我們討論的最後一個json原生資料型別是物件,在其它語言中稱為雜湊 雜湊map 字典 關聯陣列
內部物件經常用來嵌入一個實體或物件到其它物件中,例子
{
"tweet": "Elasticsearch is very flexible",
"user": {
"id": "@johnsmith",
"gender": "male",
"age": 26,
"name": {
"full": "John Smith",
"first": "John",
"last": "Smith"
}
}
}
- 內部物件的對映
es會動態檢測新的物件並對映它們為物件,在properties屬性下列出內部域
最外層的是根物件,裡面的是內部物件
- user和name域的對映結構與tweet型別的相同,tweet稱為根物件,除了它有一些文件元資料的頂級域外
例如_source域,和其它內部物件一樣
- 內部物件是如何索引的
lucene不理解內部物件,lucene文件是由一組鍵值對列表組成的,為了能讓es有效的索引內部類
它把我們的文件轉化成這樣
{
"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]
}
- 多級巢狀查詢(內部物件資料)
要檢視多級巢狀查詢的工作方式,首先需要一個具有巢狀欄位的索引,下面的請求用巢狀的make和model
欄位定義驅動程式索引的對映
- 新增一個drivers索引
PUT /drivers - 為drivers索引新增域的對映型別
PUT /drivers/_mapping
{
"properties": {
"driver": {
"type": "nested",
"properties": {
"last_name": {
"type": "text"
},
"vehicle": {
"type": "nested",
"properties": {
"make": {
"type": "text"
},
"model": {
"type": "nested"
}
}
}
}
}
}
}
- 接下來索引一些文件
PUT /drivers/_doc/
{
"driver" : {
"last_name" : "Hudson",
"vehicle" : [
{
"make" : "Mifune",
"model" : "Mach Five"
},
{
"make" : "Miller-Meteor",
"model" : "Ecto-1"
}
]
}
}
{
"driver" : {
"last_name" : "McQueen",
"vehicle" : [
{
"make" : "Powell Motors",
"model" : "Canyonero"
},
{
"make" : "Miller-Meteor",
"model" : "Ecto-1"
}
]
}
}
現在可以使用多級巢狀查詢,根據make和model欄位匹配文件
GET /drivers/_search
{
"query": {
"nested": {
"path": "driver",
"query": {
"nested": {
"path": "driver.vehicle",
"query": {
"match": {
"driver.vehicle.make": "mifune"
}
}
}
}
}
}
}
正常返回響應,非常強大啊!!!
參考文件
- es多個值作為關鍵字搜尋(相當於關係型資料庫中的in查詢)
GET /test/_search
{
"query": {
"bool": {
"must": [
{
"terms": {
"tags": [11, 66]
}
}
]
}
}
}
- 內部物件資料是如何被索引的
最後,考慮包含內部物件的陣列是如何被索引的。 假設我們有個 followers 陣列:
{
"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 的追隨者?”
相關內部物件被稱為 nested 物件,可以回答上面的查詢,我們稍後會在巢狀物件中介紹它。