336
技術社區[雲棲]
PostgreSQL SQL 語言:全文搜索
本文檔為PostgreSQL 9.6.0文檔,本轉載已得到原譯者彭煜瑋授權。
全文搜索(或者文本搜索)提供了確定滿足一個查詢的自然語言文檔的能力,並可以選擇將它們按照與查詢的相關度排序。最常用的搜索類型是找到所有包含給定查詢詞的文檔並按照它們與查詢的相似性順序返回它們。查詢和相似性的概念非常靈活並且依賴於特定的應用。最簡單的搜索認為查詢是一組詞而相似性是查詢詞在文檔中的頻度。
文本搜索操作符已經在數據庫中存在很多年了。PostgreSQL對文本數據類型提供了~、~*、LIKE和ILIKE操作符,但是它們缺少現代信息係統所要求的很多基本屬性:
- 即使對英語也缺乏語言的支持。正則表達式是不夠的,因為它們不能很容易地處理派生詞,例如satisfies和satisfy。你可能會錯過包含satisfies的文檔,盡管你可能想要在對於satisfy的搜索中找到它們。可以使用OR來搜索多個派生形式,但是這樣做太羅嗦也容易出錯(有些詞可能有數千種派生)。
- 它們不提供對搜索結果的排序(排名),這使它們麵對數以千計被找到的文檔時變得無效。
- 它們很慢因為沒有索引支持,因此它們必須為每次搜索處理所有的文檔。
全文索引允許文檔被預處理並且保存一個索引用於以後快速的搜索。預處理包括:
- 將文檔解析成記號。標識出多種類型的記號是有所幫助的,例如數字、詞、複雜的詞、電子郵件地址,這樣它們可以被以不同的方式處理。原則上記號分類取決於相關的應用,但是對於大部分目的都可以使用一套預定義的分類。PostgreSQL使用一個解析器來執行這個步驟。其中提供了一個標準的解析器,並且為特定的需要也可以創建定製的解析器。
- 將記號轉換成詞位。和一個記號一樣,一個詞位是一個字符串,但是它已經被正規化,這樣同一個詞的不同形式被變成一樣。例如,正規化幾乎總是包括將大寫字母轉換成小寫形式,並且經常涉及移除後綴(例如英語中的s或es)。這允許搜索找到同一個詞的變體形式,而不需要冗長地輸入所有可能的變體。此外,這個步驟通常會消除停用詞,它們是那些太普通的詞,它們對於搜索是無用的(簡而言之,記號是文檔文本的原始片段,而詞位是那些被認為對索引和搜索有用的詞)。PostgreSQL使用詞典來執行這個步驟。已經提供了多種標準詞典,並且為特定的需要也可以創建定製的詞典。
- 為搜索優化存儲預處理好的文檔。例如,每一個文檔可以被表示為正規化的詞位的一個有序數組。與詞位一起,通常還想要存儲用於近似排名的位置信息,這樣一個包含查詢詞更"密集"區域的文檔要比那些包含分散的查詢詞的文檔有更高的排名。
詞典允許對記號如何被正規化進行細粒度的控製。使用合適的詞典,你可以:
- 定義不應該被索引的停用詞。
- 使用Ispell把同義詞映射到一個單一詞。
- 使用一個分類詞典把短語映射到一個單一詞。
- 使用一個Ispell詞典把一個詞的不同變體映射到一種規範的形式。
- 使用Snowball詞幹分析器規則將一個詞的不同變體映射到一種規範的形式。
我們提供了一種數據類型tsvector來存儲預處理後的文檔,還提供了一種類型tsquery來表示處理過的查詢。有很多函數和操作符可以用於這些數據類型,其中最重要的是匹配操作符@@。全文搜索可以使用索引來加速。
1.1. 什麼是一個文檔?
一個document是在一個全文搜索係統中進行搜索的單元,例如,一篇雜誌文章或電子郵件消息。文本搜索引擎必須能夠解析文檔並存儲詞位(關鍵詞)與它們的父文檔之間的關聯。隨後,這些關聯會被用來搜索包含查詢詞的文檔。
對於PostgreSQL中的搜索,一個文檔通常是一個數據庫表中一行內的一個文本形式的域,或者可能是這類域的一個組合(連接),這些域可能存儲在多個表或者是動態獲取。換句話說,一個文檔可能從用於索引的不同部分構建,並且它可能被作為一個整體存儲在某個地方。例如:
SELECT title || ' ' || author || ' ' || abstract || ' ' || body AS document
FROM messages
WHERE mid = 12;
SELECT m.title || ' ' || m.author || ' ' || m.abstract || ' ' || d.body AS document
FROM messages m, docs d
WHERE mid = did AND mid = 12;
Note:
實際上在這些例子查詢中,coalesce應該被用來防止一個單一NULL屬性導致整個文檔的一個NULL結果。
另一種存儲文檔的可能性是作為文件係統中的簡單文本文件。在這種情況下,數據庫可以被用來存儲全文索引並執行搜索,並且某些唯一標識符可以被用來從文件係統檢索文檔。但是,從數據庫的外麵檢索文件要求超級用戶權限或者特殊函數支持,因此這種方法通常不如把所有數據放在PostgreSQL內部方便。另外,把所有東西放在數據庫內部允許方便地訪問文檔元數據來協助索引和現實。
對於文本搜索目的,每一個文檔必須被縮減成預處理後的tsvector格式。搜索和排名被整個在一個文檔的tsvector表示上執行 — 隻有當文檔被選擇來顯示給用戶時才需要檢索原始文本。我們因此經常把tsvector說成是文檔,但是當然它隻是完整文檔的一種緊湊表示。
1.2. 基本文本匹配
PostgreSQL中的全文搜索基於匹配操作符@@,它在一個tsvector(文檔)匹配一個tsquery(查詢)時返回true。哪種數據類型寫在前麵沒有影響:
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
?column?
----------
t
SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
?column?
----------
f
正如以上例子所建議的,一個tsquery並不隻是一個未經處理的文本,頂多一個tsvector是這樣。一個tsquery包含搜索術語,它們必須是已經正規化的詞位,並且可以使用 AND 、OR、NOT 以及 FOLLOWED BY 操作符結合多個術語。有幾個函數to_tsquery、plainto_tsquery以及phraseto_tsquery可用於將用戶書寫的文本轉換為正確的tsquery,它們會主要采用正則化出現在文本中的詞的方法。相似地,to_tsvector被用來解析和正規化一個文檔字符串。因此在實際上一個文本搜索匹配可能看起來更像:
SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
?column?
----------
t
注意如果這個匹配被寫成下麵這樣它將不會成功:
SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat');
?column?
----------
f
因為這裏不會發生詞rats的正規化。一個tsvector的元素是詞位,它被假定為已經正規化好,因此rats不匹配rat。
@@操作符也支持text輸出,它允許在簡單情況下跳過從文本字符串到tsvector或tsquery的顯式轉換。可用的變體是:
tsvector @@ tsquery
tsquery @@ tsvector
text @@ tsquery
text @@ text
前兩種我們已經見過。形式text @@ tsquery等價於to_tsvector(x) @@ y。形式text @@ text等價於to_tsvector(x) @@ plainto_tsquery(y)。
在tsquery中,&(AND)操作符指定它的兩個參數都必須出現在文檔中才表示匹配。類似地,|(OR)操作符指定至少一個參數必須出現,而!(NOT)操作符指定它的參數不出現才能匹配。
在<->(FOLLOWED BY) tsquery操作符的幫助下搜索可能的短語,隻有該操作符的參數的匹配是相鄰的並且符合給定順序時,該操作符才算是匹配。例如:
SELECT to_tsvector('fatal error') @@ to_tsquery('fatal <-> error');
?column?
----------
t
SELECT to_tsvector('error is not fatal') @@ to_tsquery('fatal <-> error');
?column?
----------
f
FOLLOWED BY 操作符還有一種更一般的版本,形式是<N>,其中N是一個表示匹配詞位位置之間的差。<1>和<->相同,而<2>允許剛好一個其他詞位出現在匹配之間,以此類推。當有些詞是停用詞時,phraseto_tsquery函數利用這個操作符來構造一個能夠匹配多詞短語的tsquery。例如:
SELECT phraseto_tsquery('cats ate rats');
phraseto_tsquery
-------------------------------
'cat' <-> 'ate' <-> 'rat'
SELECT phraseto_tsquery('the cats ate the rats');
phraseto_tsquery
-------------------------------
'cat' <-> 'ate' <2> 'rat'
一種有時候有用的特殊情況是,<0>可以被用來要求兩個匹配同一個詞的模式。
圓括號可以被用來控製tsquery操作符的嵌套。如果沒有圓括號,|的計算優先級最低,然後從低到高依次是&、<->、!。
1.3. 配置
前述的都是簡單的文本搜索例子。正如前麵所提到的,全文搜索功能包括做更多事情的能力:跳過索引特定詞(停用詞)、處理同義詞並使用更高級的解析,例如基於空白之外的解析。這個功能由文本搜索配置控製。PostgreSQL中有多種語言的預定義配置,並且你可以很容易地創建你自己的配置(psql的\dF命令顯示所有可用的配置)。
在安裝期間一個合適的配置將被選擇並且default_text_search_config也被相應地設置在postgresql.conf中。如果你正在對整個集簇使用相同的文本搜索配置,你可以使用在postgresql.conf中使用該值。要在集簇中使用不同的配置但是在任何一個數據庫內部使用同一種配置,使用ALTER DATABASE ... SET。否則,你可以在每個會話中設置default_text_search_config。
依賴一個配置的每一個文本搜索函數都有一個可選的regconfig參數,因此要使用的配置可以被顯式指定。隻有當這個參數被忽略時,default_text_search_config才被使用。
為了讓建立自定義文本搜索配置更容易,一個配置可以從更簡單的數據庫對象來建立。PostgreSQL的文本搜索功能提供了四類配置相關的數據庫對象:
- 文本搜索解析器將文檔拆分成記號並分類每個記號(例如,作為詞或者數字)。
- 文本搜索詞典將記號轉變成正規化的形式並拒絕停用詞。
- 文本搜索模板提供位於詞典底層的函數(一個詞典簡單地指定一個模板和一組用於模板的參數)。
- 文本搜索配置選擇一個解析器和一組用於將解析器產生的記號正規化的詞典。
文本搜索解析器和模板是從低層 C 函數構建而來,因此它要求 C 編程能力來開發新的解析器和模板,並且還需要超級用戶權限來把它們安裝到一個數據庫中(在PostgreSQL發布的contrib/區域中有一些附加的解析器和模板的例子)。由於詞典和配置隻是對底層解析器和模板的參數化和連接,不需要特殊的權限來創建一個新詞典或配置。創建定製詞典和配置的例子將在本章稍後的部分給出。
在前一節中的例子演示了使用簡單常數字符串進行全文匹配。本節展示如何搜索表數據,以及可選擇地使用索引。
2.1. 搜索一個表
可以在沒有一個索引的情況下做一次全文搜索。一個簡單的查詢將打印每一個行的title,這些行在其body域中包含詞friend:
SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');
這將還會找到相關的詞例如friends和friendly,因為這些都被約減到同一個正規化的詞位。
以上的查詢指定要使用english配置來解析和正規化字符串。我們也可以忽略配置參數:
SELECT title
FROM pgweb
WHERE to_tsvector(body) @@ to_tsquery('friend');
這個查詢將使用由default_text_search_config設置的配置。
一個更複雜的例子是選擇 10 個最近的文檔,要求它們在title或body中包含create和table:
SELECT title
FROM pgweb
WHERE to_tsvector(title || ' ' || body) @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;
為了清晰,我們忽略coalesce函數調用,它可能需要被用來查找在這兩個域之中包含NULL的行。
盡管這些查詢可以在沒有索引的情況下工作,大部分應用會發現這種方法太慢了,除了偶爾的臨時搜索。實際使用文本搜索通常要求創建一個索引。
2.2. 創建索引
我們可以創建一個GIN索引(Section 12.9)來加速文本搜索:
CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector('english', body));
注意這裏使用了to_tsvector的雙參數版本。隻有指定了一個配置名稱的文本搜索函數可以被用在表達式索引(Section 11.7)中。這是因為索引內容必須是沒有被default_text_search_config影響的。如果它們被影響,索引內容可能會不一致因為不同的項可能包含被使用不同文本搜索配置創建的tsvector,並且沒有辦法猜測哪個是哪個。也沒有可能正確地轉儲和恢複這樣的一個索引。
由於to_tsvector的雙參數版本被使用在上述的索引中,隻有一個使用了帶有相同配置名的雙參數版to_tsvector的查詢引用才能使用該索引。即,WHERE to_tsvector('english', body) @@ 'a & b' 可以使用該索引,但WHERE to_tsvector(body) @@ 'a & b'不能。這保證一個索引隻能和創建索引項時所用的相同配置一起使用。
可以建立更複雜的表達式索引,在其中配置名被另一個列指定,例如:
CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector(config_name, body));
這裏config_name是pgweb表中的一個列。這允許在同一個索引中有混合配置,同時記錄哪個配置被用於每一個索引項。例如,如果文檔集合包含不同語言的文檔,這就可能會有用。同樣,要使用索引的查詢必須被措辭成匹配,例如WHERE to_tsvector(config_name, body) @@ 'a & b'。
索引甚至可以連接列:
CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector('english', title || ' ' || body));
另一種方法是創建一個單獨的tsvector列來保存to_tsvector的輸出。這個例子是title和body的連接,使用coalesce來保證當其他域為NULL時一個域仍然能留在索引中:
ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
UPDATE pgweb SET textsearchable_index_col =
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));
然後我們創建一個GIN索引來加速搜索:
CREATE INDEX textsearch_idx ON pgweb USING GIN(textsearchable_index_col);
現在我們準備好執行一個快速的全文搜索了:
SELECT title
FROM pgweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;
在使用一個單獨的列來存儲tsvector表示時,有必要創建一個觸發器在title或body改變時保證tsvector列為當前值。Section 4.3解釋了怎樣去做。
單獨列方法相對於表達式索引的一個優勢在於,它不必為了利用索引而在查詢中顯式地指定文本搜索配置。如上述例子所示,查詢可以依賴default_text_search_config。另一個優勢是搜索將會更快,因為它不必重做to_tsvector調用來驗證索引匹配(在使用 GiST 索引時這一點比使用 GIN 索引時更重要;)。表達式索引方法更容易建立,但是它要求更少的磁盤空間,因為tsvector表示沒有被顯式地存儲下來。
要實現全文搜索必須要有一個從文檔創建tsvector以及從用戶查詢創建tsquery的函數。而且我們需要一種有用的順序返回結果,因此我們需要一個函數能夠根據文檔與查詢的相關性比較文檔。還有一點重要的是要能夠很好地顯示結果。PostgreSQL對所有這些函數都提供了支持。
3.1. 解析文檔
PostgreSQL提供了函數to_tsvector將一個文檔轉換成tsvector數據類型。
to_tsvector([ config regconfig, ] document text) returns tsvector
to_tsvector把一個文本文檔解析成記號,把記號縮減成詞位,並且返回一個tsvector,它列出了詞位以及詞位在文檔中的位置。文檔被根據指定的或默認的文本搜索配置來處理。下麵是一個簡單例子:
SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats');
to_tsvector
-----------------------------------------------------
'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4
在上麵這個例子中我們看到,作為結果的tsvector不包含詞a、on或it,詞rats變成了rat,並且標點符號-被忽略了。
to_tsvector函數在內部調用了一個解析器,它把文檔文本分解成記號並且為每一種記號分配一個類型。對於每一個記號,會去查詢一個詞典列表(Section 6),該列表會根據記號的類型而變化。第一個識別記號的詞典產生一個或多個正規化的詞位來表示該記號。例如,rats變成rat是因為一個詞典識別到該詞rats是rat的複數形式。一些詞會被識別為停用詞(Section 6.1),這將導致它們被忽略,因為它們出現得太頻繁以至於在搜索中起不到作用。在我們的例子中有a、on和it是停用詞。如果在列表中沒有詞典能識別該記號,那它將也會被忽略。在這個例子中標點符號-就屬於這種情況,因為事實上沒有詞典會給它分配記號類型(空間符號),即空間記號不會被索引。對於解析器、詞典以及要索引哪些記號類型是由所選擇的文本搜索配置(Section 7)決定的。可以在同一個數據庫中有多種不同的配置,並且有用於很多種語言的預定義配置。在我們的例子中,我們使用用於英語的默認配置english。
函數setweight可以被用來對tsvector中的項標注一個給定的權重,這裏一個權重可以是四個字母之一:A、B、C或D。這通常被用來標記來自文檔不同部分的項,例如標題對正文。稍後,這種信息可以被用來排名搜索結果。
因為to_tsvector(NULL) 將返回NULL,不論何時一個域可能為空時,我們推薦使用coalesce。下麵是我們推薦的從一個結構化文檔創建一個tsvector的方法:
UPDATE tt SET ti =
setweight(to_tsvector(coalesce(title,'')), 'A') ||
setweight(to_tsvector(coalesce(keyword,'')), 'B') ||
setweight(to_tsvector(coalesce(abstract,'')), 'C') ||
setweight(to_tsvector(coalesce(body,'')), 'D');
這裏我們已經使用了setweight在完成的tsvector標注每一個詞位的來源,並且接著將標注過的tsvector值用tsvector連接操作符||合並在一起(Section 4.1給出了關於這些操作符的細節)。
3.2. 解析查詢
PostgreSQL提供了函數to_tsquery、plainto_tsquery和phraseto_tsquery用來把一個查詢轉換成tsquery數據類型。to_tsquery提供了比plainto_tsquery和phraseto_tsquery更多的特性,但是它對其輸入要求更加嚴格。
to_tsquery([ config regconfig, ] querytext text) returns tsquery
to_tsquery從querytext創建一個tsquery值,該值由被tsquery操作符&(AND)、|(OR)、!(NOT)和<->(FOLLOWED BY)分隔的單個記號組成。 這些操作符可以使用圓括號分組。換句話說,to_tsquery的輸入必須已經遵循tsquery輸入的一般規則,如Section 8.11.2所述。區別在於基本的tsquery輸入把記號當作表麵值,而to_tsquery 會使用指定的或者默認的配置把每一個記號正規化成一個詞位,並且丟棄掉任何根據配置是停用詞的記號。例如:
SELECT to_tsquery('english', 'The & Fat & Rats');
to_tsquery
---------------
'fat' & 'rat'
和在基本tsquery輸
```入中一樣,權重可以被附加到每一個詞位來限製它隻匹配屬於那些權重的tsvector詞位。例如:
```java
SELECT to_tsquery('english', 'Fat | Rats:AB');
to_tsquery
------------------
'fat' | 'rat':AB
同樣,*可以被附加到一個詞位來指定前綴匹配:
SELECT to_tsquery('supern:*A & star:A*B');
to_tsquery
--------------------------
'supern':*A & 'star':*AB
這樣一個詞位將匹配一個tsvector中的任意以給定字符串開頭的詞。
to_tsquery也能夠接受單引號短語。當配置包括一個會在這種短語上觸發的分類詞典時就是它的主要用處。在下麵的例子中,一個分類詞典含規則supernovae stars : sn:
SELECT to_tsquery('''supernovae stars'' & !crab');
to_tsquery
---------------
'sn' & !'crab'
在沒有引號時,to_tsquery將為那些沒有被 AND、OR 或者 FOLLOWED BY 操作符分隔的記號產生一個語法錯誤。
plainto_tsquery([ config regconfig, ] querytext text) returns tsquery
plainto_tsquery將未格式化的文本querytext轉換成一個tsquery值。該文本被解析並被正規化,很像to_tsvector,然後&(AND)布爾操作符被插入到留下來的詞之間。
例子:
SELECT plainto_tsquery('english', 'The Fat Rats');
plainto_tsquery
-----------------
'fat' & 'rat'
注意plainto_tsquery不會識其輸入中的tsquery操作符、權重標簽或前綴匹配標簽:
SELECT plainto_tsquery('english', 'The Fat & Rats:C');
plainto_tsquery
---------------------
'fat' & 'rat' & 'c'
這裏,所有輸入的標點都被作為空間符號並且丟棄。
phraseto_tsquery([ config regconfig, ] querytext text) returns tsquery
phraseto_tsquery的行為很像plainto_tsquery,不過前者會在留下來的詞之間插入<->(FOLLOWED BY)操作符而不是&(AND)操作符。還有,停用詞也不是簡單地丟棄掉,而是通過插入<N>操作符(而不是<->操作符)來解釋。在搜索準確的詞位序列時這個函數很有用,因為 FOLLOWED BY 操作符不隻是檢查所有詞位的存在性,還會檢查詞位的順序。
例子:
SELECT phraseto_tsquery('english', 'The Fat Rats');
phraseto_tsquery
------------------
'fat' <-> 'rat'
和plainto_tsquery相似,phraseto_tsquery函數不會識別其輸入中的tsquery操作符、權重標簽或者前綴匹配標簽:
SELECT phraseto_tsquery('english', 'The Fat & Rats:C');
phraseto_tsquery
-----------------------------
'fat' <-> 'rat' <-> 'c'
3.3. 排名搜索結果
排名處理嚐試度量文檔和一個特定查詢的接近程度,這樣當有很多匹配時最相關的那些可以被先顯示。PostgreSQL提供了兩種預定義的排名函數,它們考慮詞法、臨近性和結構信息;即,它們考慮查詢詞在文檔中出現得有多頻繁,文檔中的詞有多接近,以及詞出現的文檔部分有多重要。不過,相關性的概念是模煳的並且與應用非常相關。不同的應用可能要求額外的信息用於排名,例如,文檔修改時間。內建的排名函數隻是例子。你可以編寫你自己的排名函數和/或把它們的結果與附加因素整合在一起來適應你的特定需求。
目前可用的兩種排名函數是:
ts_rank([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4
基於向量的匹配詞位的頻率來排名向量。
ts_rank_cd([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4
這個函數為給定文檔向量和查詢計算覆蓋密度排名,該方法在 Clarke、Cormack 和 Tudhope 於 1999 年在期刊 "Information Processing and Management" 上的文章 "Relevance Ranking for One to Three Term Queries" 文章中有描述。覆蓋密度類似於ts_rank排名,不過它會考慮匹配詞位相互之間的接近度。
這個函數要求詞位的位置信息來執行其計算。因此它會忽略tsvector中任何"被剝離的"詞位。如果在輸入中有未被剝離的詞位,結果將會是零(strip函數和tsvector中的位置信息的更多內容請見Section 4.1)。
對這兩個函數,可選的權重參數提供了為詞實例賦予更多或更少權重的能力,這種能力是依據它們被標注的情況的。權重數組指定每一類詞應該得到多重的權重,按照如下的順序:
{D-權重, C-權重, B-權重, A-權重}
如果沒有提供權重,那麼將使用這些默認值:
{0.1, 0.2, 0.4, 1.0}
通常權重被用來標記來自文檔特別區域的詞,如標題或一個初始的摘要,這樣它們可以被認為比來自文檔正文的詞更重要或更不重要。
由於一個較長的文檔有更多的機會包含一個查詢術語,因此考慮文檔的尺寸是合理的,例如一個一百個詞的文檔中有一個搜索詞的五個實例而零一個一千個詞的文檔中有該搜索詞的五個實例,則前者比後者更相關。兩種排名函數都采用一個整數正規化選項,它指定文檔長度是否影響其排名以及如何影響。該整數選項控製多個行為,因此它是一個位掩碼:你可以使用|指定一個或多個行為(例如,2|4)。
- 0(默認值)忽略文檔長度
- 1 用 1 + 文檔長度的對數除排名
- 2 用文檔長度除排名
- 4 用長度之間的平均調和距離除排名(隻被ts_rank_cd實現)
- 8 用文檔中唯一詞的數量除排名
- 16 用 1 + 文檔中唯一詞數量的對數除排名
- 32 用排名 + 1 除排名
如果多於一個標誌位被指定,轉換將根據列出的順序被應用。
值得注意的是排名函數並不使用任何全局信息,因此它不可能按照某些時候期望地產生一個公平的正規化,從 1% 或 100%。正規化選項 32 (rank/(rank+1))可以被應用來縮放所有的排名到範圍零到一,但是當然這隻是一個外觀上的改變;它不會影響搜索結果的順序。
這裏是一個例子,它隻選擇十個最高排名的匹配:
SELECT title, ts_rank_cd(textsearch, query) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
title | rank
-----------------------------------------------+----------
Neutrinos in the Sun | 3.1
The Sudbury Neutrino Detector | 2.4
A MACHO View of Galactic Dark Matter | 2.01317
Hot Gas and Dark Matter | 1.91171
The Virgo Cluster: Hot Plasma and Dark Matter | 1.90953
Rafting for Solar Neutrinos | 1.9
NGC 4650A: Strange Galaxy and Dark Matter | 1.85774
Hot Gas and Dark Matter | 1.6123
Ice Fishing for Cosmic Neutrinos | 1.6
Weak Lensing Distorts the Universe | 0.818218
這是相同的例子使用正規化的排名:
SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
title | rank
-----------------------------------------------+-------------------
Neutrinos in the Sun | 0.756097569485493
The Sudbury Neutrino Detector | 0.705882361190954
A MACHO View of Galactic Dark Matter | 0.668123210574724
Hot Gas and Dark Matter | 0.65655958650282
The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
Rafting for Solar Neutrinos | 0.655172410958162
NGC 4650A: Strange Galaxy and Dark Matter | 0.650072921219637
Hot Gas and Dark Matter | 0.617195790024749
Ice Fishing for Cosmic Neutrinos | 0.615384618911517
Weak Lensing Distorts the Universe | 0.450010798361481
排名可能會非常昂貴,因為它要求查詢每一個匹配文檔的tsvector,這可能會涉及很多I/O因而很慢。不幸的是,這幾乎不可能避免,因為實際查詢常常導致巨大數目的匹配。
3.4. 加亮結果
要表示搜索結果,理想的方式是顯示每一個文檔的一個部分並且顯示它是怎樣與查詢相關的。通常,搜索引擎顯示文檔片段時會對其中的搜索術語進行標記。PostgreSQL提供了一個函數ts_headline來實現這個功能。
ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text
ts_headline接受一個文檔和一個查詢,並且從該文檔返回一個引用,在其中來自查詢的術語會被加亮。被用來解析該文檔的配置可以用config指定;如果config被忽略,將會使用default_text_search_config配置。
如果一個options字符串被指定,它必須由一個逗號分隔的列表組成,列表中是一個或多個option=value對。可用的選項是:
- StartSel、StopSel:用來定界文檔中出現的查詢詞的字符串,這用來把它們與其他被引用的詞區分開。如果這些字符串包含空格或逗號,你必須把它們加上雙引號。
- MaxWords、MinWords:這些數字決定要輸出的最長和最短 headline。
- ShortWord:長度小於等於這個值的詞將被從一個 headline 的開頭或結尾處丟掉。默認值三消除普通英語文章。
- HighlightAll:布爾標誌,如果為true整個文檔將被用作 headline,並忽略前麵的三個參數。
- MaxFragments:要顯示的文本引用或片段的最大數量。默認值零選擇一種非片段傾向的 headline 生成方法。一個大於零的值選擇基於片段的 headline 生成。這種方法找到有盡可能多查詢詞的文本片段並且展開查詢詞周圍的那些片段。結果是查詢詞會靠近每個片段的中間並且在其兩側都有詞。每一個片段將是最多MaxWords並且長度小於等於ShortWord的詞被從每個片段的開頭或結尾丟棄。如果不是所有的查詢詞都在該文檔中找到,文檔中第一個MinWords的單一片段將被顯示。
- FragmentDelimiter:當多於一個片段被顯示時,片段將被這個字符串所分隔。
任何未指定的選項將收到這些默認值:
StartSel=<b>, StopSel=</b>,
MaxWords=35, MinWords=15, ShortWord=3, HighlightAll=FALSE,
MaxFragments=0, FragmentDelimiter=" ... "
例如:
SELECT ts_headline('english',
'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
to_tsquery('query & similarity'));
ts_headline
------------------------------------------------------------
containing given <b>query</b> terms
and return them in order of their <b>similarity</b> to the
<b>query</b>.
SELECT ts_headline('english',
'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
to_tsquery('query & similarity'),
'StartSel = <, StopSel = >');
ts_headline
-------------------------------------------------------
containing given <query> terms
and return them in order of their <similarity> to the
<query>.
ts_headline使用原始文檔,而不是一個tsvector摘要,因此它可能很慢並且應該被小心使用。一個典型的失誤是當最終隻有 10 個文檔要被顯示時為每個匹配的文檔調用ts_headline。SQL子查詢可以有助於此,這裏是一個例子:
SELECT id, ts_headline(body, q), rank
FROM (SELECT id, body, q, ts_rank_cd(ti, q) AS rank
FROM apod, to_tsquery('stars') q
WHERE ti @@ q
ORDER BY rank DESC
LIMIT 10) AS foo;
這一節描述在文本搜索中有用的一些額外的函數和操作符。
4.1. 操縱文檔
Section 3.1展示了未經處理的文本文檔如何被轉換成tsvector值。PostgreSQL也提供了用於操縱已經為tsvector形式的文檔的函數和操作符。
tsvector || tsvector
tsvector連接操作符返回一個向量,它結合了作為參數給出的兩個向量的詞位和位置信息。位置和權重標簽在連接期間被保留。出現在右手向量中的位置被使用左手向量中提到的最大位置進行偏移,這樣結果幾乎等於在兩個原始文檔字符串的連接上執行to_tsvector的結果(這種等價不是完全的,因為從左手參數的尾端移除的任何停用詞將會影響結果,而如果文本連接被使用它們就影響了右手參數中的詞位位置)。
使用向量形式的連接而不是在應用to_tsvector之前連接文本的一個優點是你可以使用不同配置來解析文檔的不同小節。此外,因為setweight函數按照相同的方式標記給定向量的所有詞位,如果你想把文檔的不同部分標注不同的權重,你就有必要解析文本並且在連接之前做setweight。
setweight(vector tsvector, weight "char") returns tsvector
setweight返回輸入向量的一個拷貝,其中每一個位置都被標注為給定的權重:A、B、C或D(D是新向量的默認值並且並不會被顯示在輸出上)。向量被連接時會保留這些標簽,允許來自文檔的不同部分的詞被排名函數給予不同的權重。
注意權重標簽是應用到位置而不是詞位。如果輸入向量已經被剝離了位置,則setweight什麼也不會做。
length(vector tsvector) returns integer
返回存儲在向量中的詞位數。
strip(vector tsvector) returns tsvector
返回一個向量,其中列出了和給定向量相同的詞位,不過沒有任何位置或者權重信息。其結果通常比未被剝離的向量小很多,但是用處也小很多。和未被剝離的向量一樣,相關度排名在已剝離的向量上也不起作用。此外,<->(FOLLOWED BY)tsquery操作符不會匹配已剝離的輸入,因為它無法確定詞位之間的距離。
Table 9-40中有tsvector相關函數的完整列表。
4.2. 操縱查詢
Section 3.2展示了未經處理的文本形式的查詢如何被轉換成tsquery值。PostgreSQL也提供了用於操縱已經是tsquery形式的查詢的函數和操作符。
tsquery && tsquery
返回用 AND 結合的兩個給定查詢。
tsquery || tsquery
返回用 OR 結合的兩個給定查詢。
!! tsquery
返回一個給定查詢的反(NOT)。
tsquery <-> tsquery
返回一個查詢,它用<->(FOLLOWED BY)tsquery操作符搜索兩個緊跟的匹配,第一個匹配符合第一個給定的查詢而第二個匹配符合第二個給定的查詢。例如:
SELECT to_tsquery('fat') <-> to_tsquery('cat | rat');
?column?
-----------------------------------
'fat' <-> 'cat' | 'fat' <-> 'rat'
tsquery_phrase(query1 tsquery, query2 tsquery [, distance integer ]) returns tsquery
返回一個查詢,它使用 tsquery操作符搜索兩個距離為distance個詞位的匹配,第一個匹配符合第一個給定的查詢而第二個匹配符合第二個給定的查詢。例如:
SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10);
tsquery_phrase
------------------
'fat' <10> 'cat'
numnode(query tsquery) returns integer
返回一個tsquery中的結點數(詞位外加操作符)。要確定查詢是否有意義或者是否隻包含停用詞時,這個函數有用,在前一種情況它返回 > 0,後一種情況返回 0。例子:
SELECT numnode(plainto_tsquery('the any'));
NOTICE: query contains only stopword(s) or doesn't contain lexeme(s), ignored
numnode
---------
0
SELECT numnode('foo & bar'::tsquery);
numnode
---------
3
querytree(query tsquery) returns text
返回一個tsquery中可以被用來搜索一個索引的部分。這個函數可用來檢測不可被索引的查詢,例如那些隻包含停用詞或者隻有否定術語的查詢。例如:
SELECT querytree(to_tsquery('!defined'));
querytree
-----------
4.2.1. 查詢重寫
ts_rewrite函數族在一個給定的tsquery中搜索一個目標子查詢的出現,並且將每一次出現替換成一個替補子查詢。本質上這個操作就是一個tsquery版本的子串替換。一個目標和替補的組合可以被看成是一個查詢重寫規則。一個這類重寫規則的集合可以是一個強大的搜索助手。例如,你可以使用同義詞擴展搜索(如,new york、big apple、nyc、gotham),或者收縮搜索來將用戶導向某些特點主題。在這個特性和分類詞典(Section 6.4)有些功能重疊。但是,你可以隨時修改一組重寫規則而無需重索引,而更新一個分類詞典則要求進行重索引才能生效。
ts_rewrite (query tsquery, target tsquery, substitute tsquery) returns tsquery
這種形式的ts_rewrite簡單地應用一個單一重寫規則:不管target出現在query中的那個地方,它都被substitute替代。例如:
SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);
ts_rewrite
------------
'b' & 'c'
ts_rewrite (query tsquery, select text) returns tsquery
這種形式的ts_rewrite接受一個開始query和一個 SQL select命令,它們以一個文本字符串的形式給出。select必須得到tsquery類型的兩列。對於select結果的每一行,在當前query值中出現的第一列值(目標)被第二列值(替補)所替換。例如:
CREATE TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');
SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases');
ts_rewrite
------------
'b' & 'c'
注意當多個重寫規則被以這種方式應用時,應用的順序很重要;因此在實際中你會要求源查詢按某些排序鍵ORDER BY。
讓我們考慮一個現實的天文學例子。我們將使用表驅動的重寫規則擴展查詢supernovae:
CREATE TABLE aliases (t tsquery primary key, s tsquery);
INSERT INTO aliases VALUES(to_tsquery('supernovae'), to_tsquery('supernovae|sn'));
SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
ts_rewrite
---------------------------------
'crab' & ( 'supernova' | 'sn' )
我們可以通過隻更新表來改變重寫規則:
UPDATE aliases
SET s = to_tsquery('supernovae|sn & !nebulae')
WHERE t = to_tsquery('supernovae');
SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
ts_rewrite
---------------------------------------------
'crab' & ( 'supernova' | 'sn' & !'nebula' )
當有很多重寫規則時,重寫可能會很慢,因為它要為為每一個可能的匹配檢查每一條規則。要過濾掉明顯不符合的規則,我們可以為tsquery類型使用包含操作符。在下麵的例子中,我們隻選擇那些可能匹配原始查詢的規則:
SELECT ts_rewrite('a & b'::tsquery,
'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t');
ts_rewrite
------------
'b' & 'c'
4.3. 用於自動更新的觸發器
當使用一個單獨的列來存儲你的文檔的tsvector表示時,有必要創建一個觸發器在文檔內容列改變時更新tsvector列。兩個內建觸發器函數可以用於這個目的,或者你可以編寫你自己的觸發器函數。
tsvector_update_trigger(tsvector_column_name, config_name, text_column_name [, ... ])
tsvector_update_trigger_column(tsvector_column_name, config_column_name, text_column_name [, ... ])
這些觸發器函數在CREATE TRIGGER命令中指定的參數控製下,自動從一個或多個文本列計算一個tsvector列。它們使用的一個例子是:
CREATE TABLE messages (
title text,
body text,
tsv tsvector
);
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);
INSERT INTO messages VALUES('title here', 'the body text is here');
SELECT * FROM messages;
title | body | tsv
------------+-----------------------+----------------------------
title here | the body text is here | 'bodi':4 'text':5 'titl':1
SELECT title, body FROM messages WHERE tsv @@ to_tsquery('title & body');
title | body
------------+-----------------------
title here | the body text is here
在創建了這個觸發器後,在title或body中的任何修改將自動地被反映到tsv中,不需要應用來操心同步的問題。
第一個觸發器參數必須是要被更新的tsvector列的名字。第二個參數指定要被用來執行轉換的文本搜索配置。對於tsvector_update_trigger,配置名被簡單地用第二個觸發器參數給出。如上所示,它必須是模式限定的,因此該觸發器行為不會因為search_path中的改變而改變。對於tsvector_update_trigger_column,第二個觸發器參數是另一個表列的名稱,它必須是類型regconfig。這允許做一種逐行的配置選擇。剩下的參數是文本列的名稱(類型為text、varchar或char)。它們將按給定的順序被包括在文檔中。NULL 值將被跳過(但是其他列仍將被索引)。
這些內建觸發器的一個限製是它們將所有輸入列同樣對待。要對列進行不同的處理 — 例如,使標題的權重和正文的不同 — 就需要編寫一個自定義觸發器。下麵是用PL/pgSQL作為觸發器語言的一個例子:
CREATE FUNCTION messages_trigger() RETURNS trigger AS $$
begin
new.tsv :=
setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
return new;
end
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE PROCEDURE messages_trigger();
記住當在觸發器內創建tsvector值時,顯式地指定配置名非常重要,這樣列的內容才不會被default_text_search_config的改變所影響。如果不這樣做很可能導致問題,例如在轉儲並重新載入之後搜索結果改變。
4.4. 收集文檔統計數據
ts_stat被用於檢查你的配置以及尋找候選的停用詞。
ts_stat(sqlquery text, [ weights text, ]
OUT word text, OUT ndoc integer,
OUT nentry integer) returns setof record
sqlquery是一個文本值,它包含一個必須返回單一tsvector列的 SQL 查詢。ts_stat執行該查詢並返回有關包含在該tsvector數據中的每一個可區分詞位(詞)的統計數據。返回的列是:
- word text — 一個詞位的值
- ndoc integer — 詞出現過的文檔(tsvector)的數量
- nentry integer — 詞出現的總次數
如果提供了權重,隻有具有其中之一權重的出現才被計算在內。
例如,要在一個文檔集合中查找十個最頻繁的詞:
SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;
同樣的要求,但是隻計算以權重A或B出現的次數:
SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;
文本搜索解析器負責把未處理的文檔文本劃分成記號並且標識每一個記號的類型,而可能的類型集合由解析器本身定義。注意一個解析器完全不會修改文本 — 它簡單地標識看似有理的詞邊界。因為這種有限的視野,對於應用相關的自定義解析器的需求就沒有自定義字典那麼強烈。目前PostgreSQL隻提供了一種內建解析器,它已經被證實對很多種應用都適用。
內建解析器被稱為pg_catalog.default。它識別 23 種記號類型,如Table 12-1所示。
Table 12-1. 默認解析器的記號類型
Note:
解析器的一個"字母"的概念由數據庫的區域設置決定,具體是lc_ctype。隻包含基本 ASCII 字母的詞被報告為一個單獨的記號類型,因為有時可以用來區別它們。在大部分歐洲語言中,記號類型word和asciiword應該被同樣對待。
email不支持 RFC 5322 定義的所有合法 email 字符。具體來說,對 email 用戶名被支持的非字母數字字符隻有句點、破折號和下劃線。
解析器有可能從同一份文本得出相互覆蓋的記號。例如,一個帶連字符的詞可能會被報告為一整個詞或者多個部分:
SELECT alias, description, token FROM ts_debug('foo-bar-beta1');
alias | description | token
-----------------+------------------------------------------+---------------
numhword | Hyphenated word, letters and digits | foo-bar-beta1
hword_asciipart | Hyphenated word part, all ASCII | foo
blank | Space symbols | -
hword_asciipart | Hyphenated word part, all ASCII | bar
blank | Space symbols | -
hword_numpart | Hyphenated word part, letters and digits | beta1
這種行為是值得要的,因為它允許對整個複合詞和每個部分進行搜索。這裏是另一個例子:
SELECT alias, description, token FROM ts_debug('https://example.com/stuff/index.html');
alias | description | token
----------+---------------+------------------------------
protocol | Protocol head | https://
url | URL | example.com/stuff/index.html
host | Host | example.com
url_path | URL path | /stuff/index.html
詞典被用來消除不被搜索考慮的詞(stop words)、並被用來正規化詞這樣同一個詞的不同派生形式將會匹配。一個被成功地正規化的詞被稱為一個詞位。除了提高搜索質量,正規化和移除停用詞減小了文檔的tsvector表示的尺寸,因而提高了性能。正規化不會總是有語言上的意義並且通常依賴於應用的語義。
一些正規化的例子:
- 語言學的 - Ispell 詞典嚐試將輸入詞縮減為一種正規化的形式;詞幹分析器詞典移除詞的結尾
- URL位置可以被規範化來得到等效的 URL 匹配:
- 顏色名可以被它們的十六進製值替換,例如red, green, blue, magenta -> FF0000, 00FF00, 0000FF, FF00FF
- 如果索引數字,我們可以移除某些小數位來縮減可能的數字的範圍,因此如果隻保留小數點後兩位,例如3.14159265359、3.1415926、3.14在正規化後會變得相同。
- 一個詞典是一個程序,它接受一個記號作為輸入,並返回:
- 如果輸入的記號對詞典是已知的,則返回一個詞位數組(注意一個記號可能產生多於一個詞位)
- 一個TSL_FILTER標誌被設置的單一詞位,用一個新記號來替換要被傳遞給後續字典的原始記號(做這件事的一個字典被稱為一個過濾字典)
- 如果字典知道該記號但它是一個停用詞,則返回一個空數組
- 如果字典不識別該輸入記號,則返回NULL
PostgreSQL為許多語言提供了預定義的字典。也有多種預定義模板可以被用於創建帶自定義參數的新詞典。每一種預定義詞典模板在下麵描述。如果沒有合適的現有模板,可以創建新的;例子見PostgreSQL發布的contrib/區域。
一個文本搜索配置把一個解析器和一組處理解析器輸出記號的詞典綁定在一起。對於每一中解析器能返回的記號類型,配置都指定了一個單獨的詞典列表。當該類型的一個記號被解析器找到時,每一個詞典都被按照順序查詢,知道某個詞典將其識別為一個已知詞。如果它被標識為一個停用詞或者沒有一個詞典識別它,它將被丟棄並且不會被索引和用於搜索。通常,第一個返回非NULL輸出的詞典決定結果,並且任何剩下的詞典都不會被查找;但是一個過濾詞典可以將給定詞替換為一個被修改的詞,它再被傳遞給後續的詞典。
配置一個詞典列表的通用規則是將最狹窄、最特定的詞典放在第一位,然後是更加通用的詞典,以一個非常通用的詞典結尾,像一個Snowball詞幹分析器或什麼都識別的simple。例如,對於一個天文學相關的搜索(astro_en 配置)我們可以把記號類型asciiword(ASCII 詞)綁定到一個天文學術語的分類詞典、一個通用英語詞典和一個Snowball英語詞幹分析器:
ALTER TEXT SEARCH CONFIGURATION astro_en
ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;
一個過濾詞典可以被放置在列表中的任意位置,除了在最後,因為過濾詞典放在最後就等於無用。過濾詞典可用於部分正規化詞來簡化後續詞典的工作。例如,一個過濾詞典可以被用來從音標字母中移除重音符號,就像unaccent模塊所做的。
6.1. 停用詞
停用詞是非常常用、在幾乎每一個文檔中出現並且沒有任何區分度的詞。因此,在全文搜索的環境中它們可以被忽略。例如,每一段英語文本都包含a和the等次,因此把它們存儲在一個索引中是沒有用處的。但是,停用詞確實會影響在tsvector中的位置,這進而會影響排名:
SELECT to_tsvector('english','in the list of stop words');
to_tsvector
----------------------------
'list':3 'stop':5 'word':6
缺失的位置 1、2、4 是因為停用詞。文檔的排名計算在使用和不使用停用詞的情況下是很不同的:
SELECT ts_rank_cd (to_tsvector('english','in the list of stop words'), to_tsquery('list & stop'));
ts_rank_cd
------------
0.05
SELECT ts_rank_cd (to_tsvector('english','list stop words'), to_tsquery('list & stop'));
ts_rank_cd
------------
0.1
如何對待停用詞是由指定詞典決定的。例如,ispell詞典首先正規化詞並且查看停用詞列表,而Snowball詞幹分析器首先檢查停用詞的列表。這種不同行為的原因是一衝降低噪聲的嚐試。
6.2. 簡單詞典
simple詞典模板的操作是將輸入記號轉換為小寫形式並且根據一個停用詞文件檢查它。如果該記號在該文件中被找到,則返回一個空數組,導致該記號被丟棄。否則,該詞的小寫形式被返回作為正規化的詞位。作為一種選擇,該詞典可以被配置為將非停用詞報告為未識別,允許它們被傳遞給列表中的下一個詞典。
下麵是一個使用simple模板的詞典定義的例子:
CREATE TEXT SEARCH DICTIONARY public.simple_dict (
TEMPLATE = pg_catalog.simple,
STOPWORDS = english
);
這裏,english是一個停用詞文件的基本名稱。該文件的全名將是$SHAREDIR/tsearch_data/english.stop,其中$SHAREDIR表示PostgreSQL安裝的共享數據目錄,通常是/usr/local/share/postgresql(如果不確定,使用pg_config --sharedir)。該文件格式是一個詞的列表,每行一個。空行和尾部的空格都被忽略,並且大寫也被折疊成小寫,但是沒有其他對該文件內容的處理。
現在我們能夠測試我們的詞典:
SELECT ts_lexize('public.simple_dict','YeS');
ts_lexize
-----------
{yes}
SELECT ts_lexize('public.simple_dict','The');
ts_lexize
-----------
{}
如果沒有在停用詞文件中找到,我們也可以選擇返回NULL而不是小寫形式的詞。這種行為可以通過設置詞典的Accept參數為false來選擇。繼續該例子:
ALTER TEXT SEARCH DICTIONARY public.simple_dict ( Accept = false );
SELECT ts_lexize('public.simple_dict','YeS');
ts_lexize
-----------
SELECT ts_lexize('public.simple_dict','The');
ts_lexize
-----------
{}
在使用默認值Accept = true,隻有把一個simple詞典放在詞典列表的尾部才有用,因為它將不會傳遞任何記號給後續的詞典。相反,Accept = false隻有當至少有一個後續詞典的情況下才有用。
Caution
大部分類型的詞典依賴於配置文件,例如停用詞文件。這些文件必須被存儲為 UTF-8 編碼。當它們被讀入服務器時,如果存在不同,它們將被翻譯成真實的數據庫編碼。
Caution
通常,當一個詞典配置文件第一次在數據庫會話中使用時,數據庫會話將隻讀取它一次。如果你修改了一個配置文件並且想強迫現有的會話取得新內容,可以在該詞典上發出一個ALTER TEXT SEARCH DICTIONARY命令。這可以是一次"假"更新,它並不實際修改任何參數值。
6.3. 同義詞詞典
這個詞典模板被用來創建用於同義詞替換的詞典。不支持短語(使用分類詞典模板(Section 16.4)可以支持)。一個同義詞詞典可以被用來解決語言學問題,例如,阻止一個英語詞幹分析器詞典把詞"Paris"縮減成"pari"。在同義詞詞典中有一行Paris paris並把它放在english_stem詞典之前就足夠了。例如:
SELECT * FROM ts_debug('english', 'Paris');
alias | description | token | dictionaries | dictionary | lexemes
-----------+-----------------+-------+----------------+--------------+---------
asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari}
CREATE TEXT SEARCH DICTIONARY my_synonym (
TEMPLATE = synonym,
SYNONYMS = my_synonyms
);
ALTER TEXT SEARCH CONFIGURATION english
ALTER MAPPING FOR asciiword
WITH my_synonym, english_stem;
SELECT * FROM ts_debug('english', 'Paris');
alias | description | token | dictionaries | dictionary | lexemes
-----------+-----------------+-------+---------------------------+------------+---------
asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris}
synonym模板要求的唯一參數是SYNONYMS,它是其配置文件的基本名 — 上例中的my_synonyms。該文件的完整名稱將是$SHAREDIR/tsearch_data/my_synonyms.syn(其中$SHAREDIR表示PostgreSQL安裝的共享數據目錄)。該文件格式是每行一個要被替換的詞,後麵跟著它的同義詞,用空白分隔。空行和結尾的空格會被忽略。
synonym模板還有一個可選的參數CaseSensitive,其默認值為false。當CaseSensitive為false時,同義詞文件中的詞被折疊成小寫,這和輸入記號一樣。當它為true時,詞和記號將不會被折疊成小寫,但是比較時就好像被折疊過一樣。
一個星號(*)可以被放置在配置文件中一個同義詞的末尾。這表示該同義詞是一個前綴。當項被用在to_tsvector()中時,星號會被忽略;當它被用在to_tsquery()中時,結果將是一個帶有前綴匹配標記器(見Section 12.3.2)的查詢項。例如,假設我們在$SHAREDIR/tsearch_data/synonym_sample.syn中有這些項:
postgres pgsql
postgresql pgsql
postgre pgsql
gogle googl
indices index*
那麼我們將得到這些結果:
mydb=# CREATE TEXT SEARCH DICTIONARY syn (template=synonym, synonyms='synonym_sample');
mydb=# SELECT ts_lexize('syn','indices');
ts_lexize
-----------
{index}
(1 row)
mydb=# CREATE TEXT SEARCH CONFIGURATION tst (copy=simple);
mydb=# ALTER TEXT SEARCH CONFIGURATION tst ALTER MAPPING FOR asciiword WITH syn;
mydb=# SELECT to_tsvector('tst','indices');
to_tsvector
-------------
'index':1
(1 row)
mydb=# SELECT to_tsquery('tst','indices');
to_tsquery
------------
'index':*
(1 row)
mydb=# SELECT 'indexes are very useful'::tsvector;
tsvector
---------------------------------
'are' 'indexes' 'useful' 'very'
(1 row)
mydb=# SELECT 'indexes are very useful'::tsvector @@ to_tsquery('tst','indices');
?column?
----------
t
(1 row)
6.4. 分類詞典
一個分類詞典(有時被簡寫成TZ)是一個詞的集合,其中包括了詞與短語之間的聯係,即廣義詞(BT)、狹義詞(NT)、首選詞、非首選詞、相關詞等。
基本上一個分類詞典會用一個首選詞替換所有非首選詞,並且也可選擇地保留原始術語用於索引。PostgreSQL的分類詞典的當前實現是同義詞詞典的一個擴展,並增加了短語支持。一個分類詞典要求一個下列格式的配置文件:
# this is a comment
sample word(s) : indexed word(s)
more sample word(s) : more indexed word(s)
...
其中冒號(:)符號扮演了一個短語及其替換之間的定界符。
一個分類詞典使用一個子詞典(在詞典的配置中指定)在檢查短語匹配之前正規化輸入文本。隻能選擇一個子詞典。如果子詞典無法識別一個詞,將報告一個錯誤。在這種情況下,你應該移除該詞的使用或者讓子詞典學會這個詞。你可以在一個被索引詞的開頭放上一個星號(*)來跳過在其上應用子詞典,但是所有采樣詞必須被子詞典知道。
如果有多個短語匹配輸入,則分類詞典選擇最長的那一個,並且使用最後的定義打破連結。
由子詞典識別的特定停用詞不能夠被指定;改用?標記任何可以出現停用詞的地方。例如,假定根據子詞典a和the是停用詞:
? one ? two : swsw
匹配a one the two和the one a two;兩者都將被swsw替換。
由於一個分類詞典具有識別短語的能力,它必須記住它的狀態並與解析器交互。一個分類詞典使用這些任務來檢查它是否應當處理下一個詞或者停止累積。分類詞典必須被小心地配置。例如,如果分類詞典被分配隻處理asciiword記號,則一個形如one 7的分類詞典定義將不會工作,因為記號類型uint沒有被分配給該分類詞典。
Caution
在索引期間要用到分類詞典,因此分類詞典參數中的任何變化都要求重索引。對於大多數其他索引類型,例如增加或移除停用詞等小改動都不會強製重索引。
6.4.1. 分類詞典配置
要定義一個新的分類詞典,可使用thesaurus模板。例如:
CREATE TEXT SEARCH DICTIONARY thesaurus_simple (
TEMPLATE = thesaurus,
DictFile = mythesaurus,
Dictionary = pg_catalog.english_stem
);
這裏:
- thesaurus_simple是新詞典的名稱
- mythesaurus是分類詞典配置文件的基礎名稱(它的全名將是$SHAREDIR/tsearch_data/mythesaurus.ths,其中$SHAREDIR表示安裝的共享數據目錄)。
- pg_catalog.english_stem是要用於分類詞典正規化的子詞典(這裏是一個 Snowball 英語詞幹分析器)。注意子詞典將擁有它自己的配置(例如停用詞),但這裏沒有展示。
現在可以在配置中把分類詞典thesaurus_simple綁定到想要的記號類型上,例如:
ALTER TEXT SEARCH CONFIGURATION russian
ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
WITH thesaurus_simple;
6.4.2. 分類詞典例子
考慮一個簡單的天文學分類詞典thesaurus_astro,它包含一些天文學詞組合:
supernovae stars : sn
crab nebulae : crab
下麵我們創建一個詞典並綁定一些記號類型到一個天文學分類詞典以及英語詞幹分析器:
CREATE TEXT SEARCH DICTIONARY thesaurus_astro 最後更新:2017-08-18 16:02:29
上一篇:
《深入理解OSGi:Equinox原理、應用與最佳實踐》一第1章 Java模塊化之路
下一篇:
PHP使用JPG生成GIF動畫圖片,基於php_imagick_st-Q8.dll
相關內容
關於翻譯《CSS - The Missing Manual》術語表的討論與建議
OSS社區工具與插件全新升級
微軟覺悟:決定下調Windows 8 OEM版售價
C# WinForm中PreviewKeyDown、KeyDown、KeyPress、KeyUp區別與聯係
jQuery Mobile的默認配置項詳解,jQuery Mobile的中文配置api,jQuery Mobile的配置說明,配置大全
這200個開發者裏,沒準就有未來的獨角獸!
德家7mall獲得2017中國創新與經濟融合發展大會兩項榮譽
android自定義相機黑屏問題
山西省醫院協會牽手科大訊飛 依托省人民醫院推進 “ AI + 醫療”落地
《Spring 3.0就這麼簡單》——1.2 實例功能概述