App下載

PostgreSQL 如何做全文搜索?

玉面郎君 2023-11-06 16:12:44 瀏覽數(shù) (2935)
反饋

微信截圖_20231106161215

文本搜索操作符已經(jīng)在數(shù)據(jù)庫中存在很多年了。PostgreSQL對文本數(shù)據(jù)類型提供了~、~*、LIKE和ILIKE操作符,但是它們缺少現(xiàn)代信息系統(tǒng)所要求的很多基本屬性:

  • 即使對英語也缺乏語言的支持。正則表達式是不夠的,因為它們不能很容易地處理派生詞,例如satisfies和satisfy。你可能會錯過包含satisfies的文檔,盡管你可能想要在對于satisfy的搜索中找到它們。可以使用OR來搜索多個派生形式,但是這樣做太羅嗦也容易出錯(有些詞可能有數(shù)千種派生)。
  • 它們不提供對搜索結果的排序(排名),這使它們面對數(shù)以千計被找到的文檔時變得無效。
  • 它們很慢因為沒有索引支持,因此它們必須為每次搜索處理所有的文檔。

全文索引允許文檔被預處理并且保存一個索引用于以后快速的搜索。預處理包括:

  • 將文檔解析成記號。標識出多種類型的記號是有所幫助的,例如數(shù)字、詞、復雜的詞、電子郵件地址,這樣它們可以被以不同的方式處理。原則上記號分類取決于相關的應用,但是對于大部分目的都可以使用一套預定義的分類。PostgreSQL使用一個解析器來執(zhí)行這個步驟。其中提供了一個標準的解析器,并且為特定的需要也可以創(chuàng)建定制的解析器。
  • 將記號轉換成詞位。和一個記號一樣,一個詞位是一個字符串,但是它已經(jīng)被正規(guī)化,這樣同一個詞的不同形式被變成一樣。例如,正規(guī)化幾乎總是包括將大寫字母轉換成小寫形式,并且經(jīng)常涉及移除后綴(例如英語中的s或es)。這允許搜索找到同一個詞的變體形式,而不需要冗長地輸入所有可能的變體。此外,這個步驟通常會消除停用詞,它們是那些太普通的詞,它們對于搜索是無用的(簡而言之,記號是文檔文本的原始片段,而詞位是那些被認為對索引和搜索有用的詞)。PostgreSQL使用詞典來執(zhí)行這個步驟。已經(jīng)提供了多種標準詞典,并且為特定的需要也可以創(chuàng)建定制的詞典。
  • 為搜索優(yōu)化存儲預處理好的文檔。例如,每一個文檔可以被表示為正規(guī)化的詞位的一個有序數(shù)組。與詞位一起,通常還想要存儲用于近似排名的位置信息,這樣一個包含查詢詞更“密集”區(qū)域的文檔要比那些包含分散的查詢詞的文檔有更高的排名。

詞典允許對記號如何被正規(guī)化進行細粒度的控制。使用合適的詞典,你可以:

  • 定義不應該被索引的停用詞。
  • 使用Ispell把同義詞映射到一個單一詞。
  • 使用一個分類詞典把短語映射到一個單一詞。
  • 使用一個Ispell詞典把一個詞的不同變體映射到一種規(guī)范的形式。
  • 使用Snowball詞干分析器規(guī)則將一個詞的不同變體映射到一種規(guī)范的形式。

我們提供了一種數(shù)據(jù)類型tsvector來存儲預處理后的文檔,還提供了一種類型tsquery來表示處理過的查詢。有很多函數(shù)和操作符可以用于這些數(shù)據(jù)類型,其中最重要的是匹配操作符@@。全文搜索可以使用索引來加速。

什么是一個文檔?

一個document是在一個全文搜索系統(tǒng)中進行搜索的單元,例如,一篇雜志文章或電子郵件消息。文本搜索引擎必須能夠解析文檔并存儲詞位(關鍵詞)與它們的父文檔之間的關聯(lián)。隨后,這些關聯(lián)會被用來搜索包含查詢詞的文檔。

對于PostgreSQL中的搜索,一個文檔通常是一個數(shù)據(jù)庫表中一行內的一個文本形式的域,或者可能是這類域的一個組合(連接),這些域可能存儲在多個表或者是動態(tài)獲取。換句話說,一個文檔可能從用于索引的不同部分構建,并且它可能被作為一個整體存儲在某個地方。例如:

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;

注意

實際上在這些例子查詢中,coalesce應該被用來防止一個單一NULL屬性導致整個文檔的一個NULL結果。

另一種存儲文檔的可能性是作為文件系統(tǒng)中的簡單文本文件。在這種情況下,數(shù)據(jù)庫可以被用來存儲全文索引并執(zhí)行搜索,并且某些唯一標識符可以被用來從文件系統(tǒng)檢索文檔。但是,從數(shù)據(jù)庫的外面檢索文件要求超級用戶權限或者特殊函數(shù)支持,因此這種方法通常不如把所有數(shù)據(jù)放在PostgreSQL內部方便。另外,把所有東西放在數(shù)據(jù)庫內部允許方便地訪問文檔元數(shù)據(jù)來協(xié)助索引和現(xiàn)實。

對于文本搜索目的,每一個文檔必須被縮減成預處理后的tsvector格式。搜索和排名被整個在一個文檔的tsvector表示上執(zhí)行 — 只有當文檔被選擇來顯示給用戶時才需要檢索原始文本。我們因此經(jīng)常把tsvector說成是文檔,但是當然它只是完整文檔的一種緊湊表示。

基本文本匹配

PostgreSQL中的全文搜索基于匹配操作符@@,它在一個tsvector(文檔)匹配一個tsquery(查詢)時返回true。哪種數(shù)據(jù)類型寫在前面沒有影響:

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并不只是一個未經(jīng)處理的文本,頂多一個tsvector是這樣。一個tsquery包含搜索術語,它們必須是已經(jīng)正規(guī)化的詞位,并且可以使用 AND 、OR、NOT 以及 FOLLOWED BY 操作符結合多個術語。有幾個函數(shù)to_tsquery、plainto_tsquery以及phraseto_tsquery可用于將用戶書寫的文本轉換為正確的tsquery,它們會主要采用正則化出現(xiàn)在文本中的詞的方法。相似地,to_tsvector被用來解析和正規(guī)化一個文檔字符串。因此在實際上一個文本搜索匹配可能看起來更像:

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

因為這里不會發(fā)生詞rats的正規(guī)化。一個tsvector的元素是詞位,它被假定為已經(jīng)正規(guī)化好,因此rats不匹配rat。

@@操作符也支持text輸出,它允許在簡單情況下跳過從文本字符串到tsvector或tsquery的顯式轉換??捎玫淖凅w是:

tsvector @@ tsquery
tsquery  @@ tsvector
text @@ tsquery
text @@ text

前兩種我們已經(jīng)見過。形式text @@ tsquery等價于to_tsvector(x) @@ y。形式text @@ text等價于to_tsvector(x) @@ plainto_tsquery(y)。

在tsquery中,&(AND)操作符指定它的兩個參數(shù)都必須出現(xiàn)在文檔中才表示匹配。類似地,|(OR)操作符指定至少一個參數(shù)必須出現(xiàn),而!(NOT)操作符指定它的參數(shù)不出現(xiàn)才能匹配。例如,查詢fat & ! rat匹配包含fat但不包含rat的文檔。

在<->(FOLLOWED BY) tsquery操作符的幫助下搜索可能的短語,只有該操作符的參數(shù)的匹配是相鄰的并且符合給定順序時,該操作符才算是匹配。例如:

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>允許剛好一個其他詞位出現(xiàn)在匹配之間,以此類推。當有些詞是停用詞時,phraseto_tsquery函數(shù)利用這個操作符來構造一個能夠匹配多詞短語的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操作符的嵌套。如果沒有圓括號,|的計算優(yōu)先級最低,然后從低到高依次是&、<->、!。

值得注意的是,當AND/OR/NOT操作符在一個FOLLOWED BY操作符的參數(shù)中時,它們表示與不在那些參數(shù)中時不同的含義,因為在FOLLOWED BY中匹配的準確位置是有意義的。例如,通常!x僅匹配在任何地方都不包含x的文檔。但如果y不是緊接在一個x后面,!x <-> y就會匹配那個y,在文檔中其他位置出現(xiàn)的x不會阻止匹配。另一個例子是,x & y通常僅要求x和y均出現(xiàn)在文檔中的某處,但是(x & y) <-> z要求x和y在緊挨著z之前的同一個位置匹配。因此這個查詢的行為會不同于x <-> z & y <-> z,它將匹配一個含有兩個單獨序列x z以及y z的文檔(這個特定的查詢一點用都沒有,因為x和y不可能在同一個位置匹配,但是對于前綴匹配模式之類的更復雜的情況,這種形式的查詢就會有用武之地)。

配置

前述的都是簡單的文本搜索例子。正如前面所提到的,全文搜索功能包括做更多事情的能力:跳過索引特定詞(停用詞)、處理同義詞并使用更高級的解析,例如基于空白之外的解析。這個功能由文本搜索配置控制。PostgreSQL中有多種語言的預定義配置,并且你可以很容易地創(chuàng)建你自己的配置(psql的\dF命令顯示所有可用的配置)。

在安裝期間一個合適的配置將被選擇并且default_text_search_config也被相應地設置在postgresql.conf中。如果你正在對整個集簇使用相同的文本搜索配置,你可以使用在postgresql.conf中使用該值。要在集簇中使用不同的配置但是在任何一個數(shù)據(jù)庫內部使用同一種配置,使用ALTER DATABASE ... SET。否則,你可以在每個會話中設置default_text_search_config。

依賴一個配置的每一個文本搜索函數(shù)都有一個可選的regconfig參數(shù),因此要使用的配置可以被顯式指定。只有當這個參數(shù)被忽略時,default_text_search_config才被使用。

為了讓建立自定義文本搜索配置更容易,一個配置可以從更簡單的數(shù)據(jù)庫對象來建立。PostgreSQL的文本搜索功能提供了四類配置相關的數(shù)據(jù)庫對象:

  • 文本搜索解析器將文檔拆分成記號并分類每個記號(例如,作為詞或者數(shù)字)。
  • 文本搜索詞典將記號轉變成正規(guī)化的形式并拒絕停用詞。
  • 文本搜索模板提供位于詞典底層的函數(shù)(一個詞典簡單地指定一個模板和一組用于模板的參數(shù))。
  • 文本搜索配置選擇一個解析器和一組用于將解析器產(chǎn)生的記號正規(guī)化的詞典。

文本搜索解析器和模板是從低層 C 函數(shù)構建而來,因此它要求 C 編程能力來開發(fā)新的解析器和模板,并且還需要超級用戶權限來把它們安裝到一個數(shù)據(jù)庫中(在PostgreSQL發(fā)布的contrib/區(qū)域中有一些附加的解析器和模板的例子)。由于詞典和配置只是對底層解析器和模板的參數(shù)化和連接,不需要特殊的權限來創(chuàng)建一個新詞典或配置。創(chuàng)建定制詞典和配置的例子將在本章稍后的部分給出。


搜索一個表

可以在沒有一個索引的情況下做一次全文搜索。一個簡單的查詢將打印每一個行的title,這些行在其body域中包含詞friend:

SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');

這將還會找到相關的詞例如friends和friendly,因為這些都被約減到同一個正規(guī)化的詞位。

以上的查詢指定要使用english配置來解析和正規(guī)化字符串。我們也可以忽略配置參數(shù):

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函數(shù)調用,它可能需要被用來查找在這兩個域之中包含NULL的行。

盡管這些查詢可以在沒有索引的情況下工作,大部分應用會發(fā)現(xiàn)這種方法太慢了,除了偶爾的臨時搜索。實際使用文本搜索通常要求創(chuàng)建一個索引。

創(chuàng)建索引

我們可以創(chuàng)建一個GIN索引來加速文本搜索:

CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector('english', body));

注意這里使用了to_tsvector的雙參數(shù)版本。只有指定了一個配置名稱的文本搜索函數(shù)可以被用在表達式索引中。這是因為索引內容必須是沒有被default_text_search_config影響的。如果它們被影響,索引內容可能會不一致因為不同的項可能包含被使用不同文本搜索配置創(chuàng)建的tsvector,并且沒有辦法猜測哪個是哪個。也沒有可能正確地轉儲和恢復這樣的一個索引。

由于to_tsvector的雙參數(shù)版本被使用在上述的索引中,只有一個使用了帶有相同配置名的雙參數(shù)版to_tsvector的查詢引用才能使用該索引。即,WHERE to_tsvector('english', body) @@ 'a & b' 可以使用該索引,但WHERE to_tsvector(body) @@ 'a & b'不能。這保證一個索引只能和創(chuàng)建索引項時所用的相同配置一起使用。

可以建立更復雜的表達式索引,在其中配置名被另一個列指定,例如:

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));

另一種方法是創(chuàng)建一個單獨的tsvector列來保存to_tsvector的輸出。若要使此列與其源數(shù)據(jù)保持自動更新,用存儲生成的列。這個例子是title和body的連接,使用coalesce來保證當其他域為NULL時一個域仍然能留在索引中:

ALTER TABLE pgweb
    ADD COLUMN textsearchable_index_col tsvector
               GENERATED ALWAYS AS (to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;

然后我們創(chuàng)建一個GIN索引來加速搜索:

CREATE INDEX textsearch_idx ON pgweb USING GIN(textsearchable_index_col);

現(xiàn)在我們準備好執(zhí)行一個快速的全文搜索了:

SELECT title
FROM pgweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;

單獨列方法相對于表達式索引的一個優(yōu)勢在于,它不必為了利用索引而在查詢中顯式地指定文本搜索配置。如上述例子所示,查詢可以依賴default_text_search_config。另一個優(yōu)勢是搜索將會更快,因為它不必重做to_tsvector調用來驗證索引匹配(在使用 GiST 索引時這一點比使用 GIN 索引時更重要;)。表達式索引方法更容易建立,但是它要求更少的磁盤空間,因為tsvector表示沒有被顯式地存儲下來。


解析文檔

PostgreSQL提供了函數(shù)to_tsvector將一個文檔轉換成tsvector數(shù)據(jù)類型。

to_tsvector([ config regconfig, ] document text) returns tsvector

to_tsvector把一個文本文檔解析成記號,把記號縮減成詞位,并且返回一個tsvector,它列出了詞位以及詞位在文檔中的位置。文檔被根據(jù)指定的或默認的文本搜索配置來處理。下面是一個簡單例子:

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函數(shù)在內部調用了一個解析器,它把文檔文本分解成記號并且為每一種記號分配一個類型。對于每一個記號,會去查詢一個詞典列表,該列表會根據(jù)記號的類型而變化。第一個識別記號的詞典產(chǎn)生一個或多個正規(guī)化的詞位來表示該記號。例如,rats變成rat是因為一個詞典識別到該詞rats是rat的復數(shù)形式。一些詞會被識別為停用詞,這將導致它們被忽略,因為它們出現(xiàn)得太頻繁以至于在搜索中起不到作用。在我們的例子中有a、on和it是停用詞。如果在列表中沒有詞典能識別該記號,那它將也會被忽略。在這個例子中標點符號-就屬于這種情況,因為事實上沒有詞典會給它分配記號類型(空間符號),即空間記號不會被索引。對于解析器、詞典以及要索引哪些記號類型是由所選擇的文本搜索配置決定的??梢栽谕粋€數(shù)據(jù)庫中有多種不同的配置,并且有用于很多種語言的預定義配置。在我們的例子中,我們使用用于英語的默認配置english。

函數(shù)setweight可以被用來對tsvector中的項標注一個給定的權重,這里一個權重可以是四個字母之一:A、B、C或D。這通常被用來標記來自文檔不同部分的項,例如標題對正文。稍后,這種信息可以被用來排名搜索結果。

因為to_tsvector(NULL) 將返回NULL,不論何時一個域可能為空時,我們推薦使用coalesce。下面是我們推薦的從一個結構化文檔創(chuàng)建一個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');

這里我們已經(jīng)使用了setweight在完成的tsvector標注每一個詞位的來源,并且接著將標注過的tsvector值用tsvector連接操作符||合并在一起。

解析查詢

PostgreSQL提供了函數(shù)to_tsquery、plainto_tsquery、phraseto_tsquery以及websearch_to_tsquery用來把一個查詢轉換成tsquery數(shù)據(jù)類型。to_tsquery提供了比plainto_tsquery和phraseto_tsquery更多的特性,但是它對其輸入要求更加嚴格。websearch_to_tsquery是to_tsquery的一個簡化版本,它使用一種可選擇的語法,類似于Web搜索引擎使用的語法。

to_tsquery([ config regconfig, ] querytext text) returns tsquery

to_tsquery從querytext創(chuàng)建一個tsquery值,該值由被tsquery操作符&(AND)、|(OR)、!(NOT)和<->(FOLLOWED BY)分隔的單個記號組成。 這些操作符可以使用圓括號分組。換句話說,to_tsquery的輸入必須已經(jīng)遵循tsquery輸入的一般規(guī)則。區(qū)別在于基本的tsquery輸入把記號當作表面值,而to_tsquery 會使用指定的或者默認的配置把每一個記號正規(guī)化成一個詞位,并且丟棄掉任何根據(jù)配置是停用詞的記號。例如:

SELECT to_tsquery('english', 'The & Fat & Rats');
  to_tsquery   
---------------
 'fat' & 'rat'

和在基本tsquery輸入中一樣,權重可以被附加到每一個詞位來限制它只匹配屬于那些權重的tsvector詞位。例如:

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也能夠接受單引號短語。當配置包括一個會在這種短語上觸發(fā)的分類詞典時就是它的主要用處。在下面的例子中,一個分類詞典含規(guī)則supernovae stars : sn:

SELECT to_tsquery('''supernovae stars'' & !crab');
  to_tsquery
---------------
 'sn' & !'crab'

在沒有引號時,to_tsquery將為那些沒有被 AND、OR 或者 FOLLOWED BY 操作符分隔的記號產(chǎn)生一個語法錯誤。

plainto_tsquery([ config regconfig, ] querytext text) returns tsquery

plainto_tsquery將未格式化的文本querytext轉換成一個tsquery值。該文本被解析并被正規(guī)化,很像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>操作符(而不是<->操作符)來解釋。在搜索準確的詞位序列時這個函數(shù)很有用,因為 FOLLOWED BY 操作符不只是檢查所有詞位的存在性,還會檢查詞位的順序。

例子:

SELECT phraseto_tsquery('english', 'The Fat Rats');
 phraseto_tsquery
------------------
 'fat' <-> 'rat'

和plainto_tsquery相似,phraseto_tsquery函數(shù)不會識別其輸入中的tsquery操作符、權重標簽或者前綴匹配標簽:

SELECT phraseto_tsquery('english', 'The Fat & Rats:C');
      phraseto_tsquery
-----------------------------
 'fat' <-> 'rat' <-> 'c'
websearch_to_tsquery([ config regconfig, ] querytext text) returns tsquery

websearch_to_tsquery使用一種可供選擇的語法從querytext創(chuàng)建一個tsquery值,這種語法中簡單的未格式化文本是一個有效的查詢。和plainto_tsquery以及phraseto_tsquery不同,它還識別特定的操作符。此外,這個函數(shù)絕不會報出語法錯誤,這就可以把原始的用戶提供的輸入用于搜索。支持下列語法:

  • 無引號文本:不在引號中的文本將被轉換成由&操作符分隔的詞,就像被plainto_tsquery處理過那樣。
  • "引號文本":在引號中的文本將被轉換成由<->操作符分隔的詞,就像被phraseto_tsquery處理過那樣。
  • OR:邏輯或將被轉換成|操作符。
  • -:邏輯非操作符,被轉換成!操作符。

示例:

SELECT websearch_to_tsquery('english', 'The fat rats');
 websearch_to_tsquery
----------------------
 'fat' & 'rat'
(1 row)

SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');
       websearch_to_tsquery
----------------------------------
 'supernova' <-> 'star' & !'crab'
(1 row)

SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');
       websearch_to_tsquery
-----------------------------------
 'sad' <-> 'cat' | 'fat' <-> 'rat'
(1 row)

SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"');
         websearch_to_tsquery
---------------------------------------
 'signal' & !( 'segment' <-> 'fault' )
(1 row)

SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->');
 websearch_to_tsquery
----------------------
 'dummi' & 'queri'
(1 row)

排名搜索結果

排名處理嘗試度量文檔和一個特定查詢的接近程度,這樣當有很多匹配時最相關的那些可以被先顯示。PostgreSQL提供了兩種預定義的排名函數(shù),它們考慮詞法、臨近性和結構信息;即,它們考慮查詢詞在文檔中出現(xiàn)得有多頻繁,文檔中的詞有多接近,以及詞出現(xiàn)的文檔部分有多重要。不過,相關性的概念是模糊的并且與應用非常相關。不同的應用可能要求額外的信息用于排名,例如,文檔修改時間。內建的排名函數(shù)只是例子。你可以編寫你自己的排名函數(shù)和/或把它們的結果與附加因素整合在一起來適應你的特定需求。

目前可用的兩種排名函數(shù)是:

ts_rank([ weights float4[], ] vector tsvectorquery tsquery [, normalization integer ]) returns float4

基于向量的匹配詞位的頻率來排名向量。

ts_rank_cd([ weights float4[], ] vector tsvectorquery tsquery [, normalization integer ]) returns float4

這個函數(shù)為給定文檔向量和查詢計算覆蓋密度排名,該方法在 Clarke、Cormack 和 Tudhope 于 1999 年在期刊 "Information Processing and Management" 上的文章 "Relevance Ranking for One to Three Term Queries" 文章中有描述。覆蓋密度類似于ts_rank排名,不過它會考慮匹配詞位相互之間的接近度。

這個函數(shù)要求詞位的位置信息來執(zhí)行其計算。因此它會忽略tsvector中任何“被剝離的”詞位。如果在輸入中有未被剝離的詞位,結果將會是零。

對這兩個函數(shù),可選的權重參數(shù)提供了為詞實例賦予更多或更少權重的能力,這種能力是依據(jù)它們被標注的情況的。權重數(shù)組指定每一類詞應該得到多重的權重,按照如下的順序:

{D-權重, C-權重, B-權重, A-權重}

如果沒有提供權重,那么將使用這些默認值:

{0.1, 0.2, 0.4, 1.0}

通常權重被用來標記來自文檔特別區(qū)域的詞,如標題或一個初始的摘要,這樣它們可以被認為比來自文檔正文的詞更重要或更不重要。

由于一個較長的文檔有更多的機會包含一個查詢術語,因此考慮文檔的尺寸是合理的,例如一個一百個詞的文檔中有一個搜索詞的五個實例而零一個一千個詞的文檔中有該搜索詞的五個實例,則前者比后者更相關。兩種排名函數(shù)都采用一個整數(shù)正規(guī)化選項,它指定文檔長度是否影響其排名以及如何影響。該整數(shù)選項控制多個行為,因此它是一個位掩碼:你可以使用|指定一個或多個行為(例如,2|4)。

  • 0(默認值)忽略文檔長度
  • 1 用 1 + 文檔長度的對數(shù)除排名
  • 2 用文檔長度除排名
  • 4 用長度之間的平均調和距離除排名(只被ts_rank_cd實現(xiàn))
  • 8 用文檔中唯一詞的數(shù)量除排名
  • 16 用 1 + 文檔中唯一詞數(shù)量的對數(shù)除排名
  • 32 用排名 + 1 除排名

如果多于一個標志位被指定,轉換將根據(jù)列出的順序被應用。

值得注意的是排名函數(shù)并不使用任何全局信息,因此它不可能按照某些時候期望地產(chǎn)生一個公平的正規(guī)化,從 1% 或 100%。正規(guī)化選項 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

這是相同的例子使用正規(guī)化的排名:

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因而很慢。不幸的是,這幾乎不可能避免,因為實際查詢常常導致巨大數(shù)目的匹配。

加亮結果

要表示搜索結果,理想的方式是顯示每一個文檔的一個部分并且顯示它是怎樣與查詢相關的。通常,搜索引擎顯示文檔片段時會對其中的搜索術語進行標記。PostgreSQL提供了一個函數(shù)ts_headline來實現(xiàn)這個功能。

ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text

ts_headline接受一個文檔和一個查詢,并且從該文檔返回一個引用,在其中來自查詢的術語會被加亮。被用來解析該文檔的配置可以用config指定;如果config被忽略,將會使用default_text_search_config配置。

如果一個options字符串被指定,它必須由一個逗號分隔的列表組成,列表中是一個或多個option=value對??捎玫倪x項是:

  • StartSel、StopSel:用來定界文檔中出現(xiàn)的查詢詞的字符串,這用來把它們與其他被引用的詞區(qū)分開。如果這些字符串包含空格或逗號,你必須把它們加上雙引號。
  • MaxWords、MinWords:這些數(shù)字決定要輸出的最長和最短 headline。
  • ShortWord:長度小于等于這個值的詞將被從一個 headline 的開頭或結尾處丟掉。默認值三消除普通英語文章。
  • HighlightAll:布爾標志,如果為true整個文檔將被用作 headline,并忽略前面的三個參數(shù)。
  • MaxFragments:要顯示的文本引用或片段的最大數(shù)量。默認值零選擇一種非片段傾向的 headline 生成方法。一個大于零的值選擇基于片段的 headline 生成。這種方法找到有盡可能多查詢詞的文本片段并且展開查詢詞周圍的那些片段。結果是查詢詞會靠近每個片段的中間并且在其兩側都有詞。每一個片段將是最多MaxWords并且長度小于等于ShortWord的詞被從每個片段的開頭或結尾丟棄。如果不是所有的查詢詞都在該文檔中找到,文檔中第一個MinWords的單一片段將被顯示。
  • FragmentDelimiter:當多于一個片段被顯示時,片段將被這個字符串所分隔。

這些選項名稱不區(qū)分大小寫。任何未指定的選項將收到這些默認值:

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摘要,因此它可能很慢并且應該被小心使用。


0 人點贊