ElasticSearch 結構化搜索


  1、介紹

    結構化搜索(Structured search) 是指有關探詢那些具有內在結構數據的過程。比如日期、時間和數字都是結構化的:它們有精確的格式,我們可以對這些格式進行邏輯操作。

    比較常見的操作包括比較數字或時間的范圍,或判定兩個值的大小。

 

    文本也可以是結構化的。如彩色筆可以有離散的顏色集合: 紅(red) 、 綠(green) 、 藍(blue) 。一個博客可能被標記了關鍵詞 分布式(distributed) 和 搜索(search) 。

    電商網站上的商品都有 UPCs(通用產品碼 Universal Product Codes)或其他的唯一標識,它們都需要遵從嚴格規定的、結構化的格式。

 

    在結構化查詢中,我們得到的結果 總是 非是即否,要么存於集合之中,要么存在集合之外。結構化查詢不關心文件的相關度或評分;它簡單的對文檔包括或排除處理。

    這在邏輯上是能說通的,因為一個數字不能比其他數字  適合存於某個相同范圍。結果只能是:存於范圍之中,抑或反之。同樣,對於結構化文本來說,一個值要么相等,要么不等。沒有 更似 這種概念。

    當進行精確值查找時, 要使用過濾器(filters)。過濾器很重要,因為它們執行速度非常快,不會計算相關度(直接跳過了整個評分階段)而且很容易被緩存,因此盡可能多的使用過濾式查詢。

   

  2、term查詢數字

    最為常用的 term 查詢, 可以用它處理數字(numbers)、布爾值(Booleans)、日期(dates)以及文本(text)

    創建並索引一些表示產品的文檔,文檔里有字段 `price` 和 `productID` ( `價格` 和 `產品ID` ):

    

POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

    

    通常當查找一個精確值的時候,我們不希望對查詢進行評分計算。只希望對文檔進行包括或排除的計算,所以我們會使用 constant_score 查詢以非評分模式來執行 term 查詢並以一作為統一評分。

    最終組合的結果是一個 constant_score 查詢,它包含一個 term 查詢:

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "price" : 10
                }
            }
        }
    }
}

 

    

    我們用 constant_score 將 term 查詢轉化成為過濾器,這個查詢所搜索到的結果與我們期望的一致:只有文檔 1 命中並作為結果返回(因為只有 1 的價格是 10)

 

  3、term查詢文本

    使用 term 查詢匹配字符串和匹配數字一樣容易。例如查詢產品號是XHDK-A-1293-#fJ3 的數據,也就是查詢文檔1

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

 

    

    顯然沒有查詢到想要的結果,為什么呢?問題不在 term 查詢,而在於索引數據的方式,先查看productID的索引方式

GET /my_store/_analyze
{
  "field": "productID",
  "text": "XHDK-A-1293-#fJ3"
}

    

    通過上面的結果,可以看到"XHDK-A-1293-#fJ3"這個數據被分成了四個部分,所以當我們用 term 查詢查找精確值 XHDK-A-1293-#fJ3 的時候,找不到任何文檔,因為它並不在我們的倒排索引中,

    顯然這種對 ID 碼或其他任何精確值的處理方式並不是我們想要的。

    為了避免這種問題,我們需要告訴 Elasticsearch 該字段具有精確值,要將其設置成 not_analyzed 無需分析的。 

DELETE /my_store 

PUT /my_store 
{
    "mappings" : {
        "products" : {
            "properties" : {
                "productID" : {
                    "type" : "string",
                    "index" : "not_analyzed" 
                }
            }
        }
    }

}

    

    注意:對Elastic 5.5版本以后的,string被text代替了,不過string還能用,而index對應的值是true或false。對應string類型的數據而言,not_analyzed這個數據還可以用,但是針對string類型數據,其它類型的數據不行。

    刪除索引是必須的,因為我們不能更新已存在的映射。

    在索引被刪除后,我們可以創建新的索引並為其指定自定義映射。

    這里我們告訴 Elasticsearch ,我們不想對 productID 做任何分析。

    現在我們可以為文檔重建索引:

POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

    再次查看productID的索引方式:

    

    顯然XHDK-A-1293-#fJ3數據沒有被分析

 

    重新查詢產品號是XHDK-A-1293-#fJ3 的數據

    

    查詢成功

 

  4、查找多個精確值

    

    term 查詢對於查找單個值非常有用,但通常我們可能想搜索多個值。 如果我們想要查找價格字段值為 $20 或 $30 的文檔該如何處理呢?

    不需要使用多個 term 查詢,我們只要用單個 terms 查詢(注意末尾的 s ), terms 查詢好比是 term 查詢的復數形式(以英語名詞的單復數做比)。

    它幾乎與 term 的使用方式一模一樣,與指定單個價格不同,我們只要將 term 字段的值改為數組即可:

    與 term 查詢一樣,也需要將其置入 filter 語句的常量評分查詢中使用:

    

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "terms" : { 
                    "price" : [20, 30]
                }
            }
        }
    }
}

    運行結果返回第二、第三和第四個文檔:

    

 

     包含而不是相等

    

    一定要了解 term 和 terms 是 包含(contains) 操作,而非 等值(equals) (判斷)。 如何理解這句話呢?

    如果我們有一個 term(詞項)過濾器 { "term" : { "tags" : "search" } } ,它會與以下兩個文檔 同時匹配

 

  5、范圍查找

    實際上,對數字范圍進行過濾有時會更有用。例如,我們可能想要查找所有價格大於 $20 且小於 $40 美元的產品。

    在 SQL 中,范圍查詢可以表示為:

    

    Elasticsearch 有 range 查詢, 不出所料地,可以用它來查找處於某個范圍內的文檔:

    

    

    range 查詢可同時提供包含(inclusive)和不包含(exclusive)這兩種范圍表達式,可供組合的選項如下:

    gt> 大於(greater than)

    lt< 小於(less than)

    gte: >= 大於或等於(greater than or equal to)

    lte<= 小於或等於(less than or equal to)

    下面是一個范圍查詢的例子:. 

  

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "range" : {
                    "price" : {
                        "gte" : 20,
                        "lt"  : 40
                    }
                }
            }
        }
    }
}

    如果想要范圍無界(比方說 >20 ),只須省略其中一邊的限制:

"range" : {
    "price" : {
        "gt" : 20
    }
}

    日期范圍

    range 查詢同樣可以應用在日期字段上:

"range" : {
    "timestamp" : {
        "gt" : "2014-01-01 00:00:00",
        "lt" : "2014-01-07 00:00:00"
    }
}

    當使用它處理日期字段時, range 查詢支持對 日期計算(date math) 進行操作,比方說,如果我們想查找時間戳在過去一小時內的所有文檔:

"range" : {
    "timestamp" : {
        "gt" : "now-1h"
    }
}

    

    這個過濾器會一直查找時間戳在過去一個小時內的所有文檔,讓過濾器作為一個時間 滑動窗口(sliding window) 來過濾文檔。

    日期計算還可以被應用到某個具體的時間,並非只能是一個像 now 這樣的占位符。只要在某個日期后加上一個雙管符號 (||) 並緊跟一個日期數學表達式就能做到:

"range" : {
    "timestamp" : {
        "gt" : "2014-01-01 00:00:00",
        "lt" : "2014-01-01 00:00:00||+1M" 
    }
}

    早於 2014 年 1 月 1 日加 1 月(2014 年 2 月 1 日 零時)

 

    字符串范圍

    range 查詢同樣可以處理字符串字段, 字符串范圍可采用 字典順序(lexicographically) 或字母順序(alphabetically)。例如,下面這些字符串是采用字典序(lexicographically)排序的:

    5, 50, 6, B, C, a, ab, abb, abc, b

    在倒排索引中的詞項就是采取字典順序(lexicographically)排列的,這也是字符串范圍可以使用這個順序來確定的原因。

    如果我們想查找從 a 到 b (不包含)的字符串,同樣可以使用 range 查詢語法:

"range" : {
    "title" : {
        "gte" : "a",
        "lt" :  "b"
    }
}

 

    

    注意基數

    數字和日期字段的索引方式使高效地范圍計算成為可能。 但字符串卻並非如此,要想對其使用范圍過濾,Elasticsearch 實際上是在為范圍內的每個詞項都執行 term 過濾器,這會比日期或數字的范圍過濾慢許多。

    字符串范圍在過濾 低基數(low cardinality) 字段(即只有少量唯一詞項)時可以正常工作,但是唯一詞項越多,字符串范圍的計算會越慢。

 

   6、處理Null值

    有的文檔有名為 tags (標簽)的字段,它是個多值字段, 一個文檔可能有一個或多個標簽,也可能根本就沒有標簽。如果一個字段沒有值,那么如何將它存入倒排索引中的呢?

    這是個有欺騙性的問題,因為答案是:什么都不存。

    如何將某個不存在的字段存儲在這個數據結構中呢?無法做到!簡單的說,一個倒排索引只是一個 token 列表和與之相關的文檔信息,如果字段不存在,那么它也不會持有任何 token,也就無法在倒排索引結構中表現。

    最終,這也就意味着 null[] (空數組)和 [null] 所有這些都是等價的,它們無法存於倒排索引中。

    顯然,世界並不簡單,數據往往會有缺失字段,或有顯式的空值或空數組。為了應對這些狀況,Elasticsearch 提供了一些工具來處理空或缺失值

 

    存在查詢

    第一件武器就是 exists 存在查詢。 這個查詢會返回那些在指定字段有任何值的文檔,讓我們索引一些示例文檔並用標簽的例子來說明:

    

    1、tags 字段有 1 個值。

    2、tags 字段有 2 個值。

    3、tags 字段缺失。

    4、tags 字段被置為 null 。

    5、tags 字段有 1 個值和 1 個 null 。

    以上文檔集合中 tags 字段對應的倒排索引如下:

    

    我們的目標是找到那些被設置過標簽字段的文檔,並不關心標簽的具體內容。只要它存在於文檔中即可,用 SQL 的話就是用 IS NOT NULL 非空進行查詢:

    

    在 Elasticsearch 中,使用 exists 查詢的方式如下:

GET /my_index/posts/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "exists" : { "field" : "tags" }
            }
        }
    }
}
#
GET /my_index/posts/_search
{
    "query" : {
        "bool" : {
          "must":{
            "exists" : { "field" : "tags" }
          }
        }
    }
}

    這個查詢返回 3 個文檔:

    

 

     盡管文檔 5 有 null 值,但它仍會被命中返回。字段之所以存在,是因為標簽有實際值( search )可以被索引,所以 null 對過濾不會產生任何影響

    顯而易見,只要 tags 字段存在項(term)的文檔都會命中並作為結果返回,只有 3 和 4 兩個文檔被排除

 

    缺失查詢

    它返回某個特定 _無_ 值字段的文檔,與以下 SQL 表達的意思類似:

     

    轉成ElasticSearch語句如下:

GET /my_index/posts/_search
{
    "query" : {
        "bool" : {
          "must_not":{
            "exists" : { "field" : "tags" }
          }
        }
    }
}

    

 

    按照期望的那樣,我們得到 3 和 4 兩個文檔(這兩個文檔的 tags 字段沒有實際值):

 


免責聲明!

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



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