App下載

PostgreSQL 如何做全文搜索?

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

微信截圖_20231106161215

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

  • 即使對(duì)英語(yǔ)也缺乏語(yǔ)言的支持。正則表達(dá)式是不夠的,因?yàn)樗鼈儾荒芎苋菀椎靥幚砼缮~,例如satisfies和satisfy。你可能會(huì)錯(cuò)過(guò)包含satisfies的文檔,盡管你可能想要在對(duì)于satisfy的搜索中找到它們??梢允褂肙R來(lái)搜索多個(gè)派生形式,但是這樣做太羅嗦也容易出錯(cuò)(有些詞可能有數(shù)千種派生)。
  • 它們不提供對(duì)搜索結(jié)果的排序(排名),這使它們面對(duì)數(shù)以千計(jì)被找到的文檔時(shí)變得無(wú)效。
  • 它們很慢因?yàn)闆](méi)有索引支持,因此它們必須為每次搜索處理所有的文檔。

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

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

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

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

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

什么是一個(gè)文檔?

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

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

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;

注意

實(shí)際上在這些例子查詢中,coalesce應(yīng)該被用來(lái)防止一個(gè)單一NULL屬性導(dǎo)致整個(gè)文檔的一個(gè)NULL結(jié)果。

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

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

基本文本匹配

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

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

正如以上例子所建議的,一個(gè)tsquery并不只是一個(gè)未經(jīng)處理的文本,頂多一個(gè)tsvector是這樣。一個(gè)tsquery包含搜索術(shù)語(yǔ),它們必須是已經(jīng)正規(guī)化的詞位,并且可以使用 AND 、OR、NOT 以及 FOLLOWED BY 操作符結(jié)合多個(gè)術(shù)語(yǔ)。有幾個(gè)函數(shù)to_tsquery、plainto_tsquery以及phraseto_tsquery可用于將用戶書(shū)寫(xiě)的文本轉(zhuǎn)換為正確的tsquery,它們會(huì)主要采用正則化出現(xiàn)在文本中的詞的方法。相似地,to_tsvector被用來(lái)解析和正規(guī)化一個(gè)文檔字符串。因此在實(shí)際上一個(gè)文本搜索匹配可能看起來(lái)更像:

SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
 ?column? 
----------
 t

注意如果這個(gè)匹配被寫(xiě)成下面這樣它將不會(huì)成功:

SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat');
 ?column? 
----------
 f

因?yàn)檫@里不會(huì)發(fā)生詞rats的正規(guī)化。一個(gè)tsvector的元素是詞位,它被假定為已經(jīng)正規(guī)化好,因此rats不匹配rat。

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

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

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

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

在<->(FOLLOWED BY) tsquery操作符的幫助下搜索可能的短語(yǔ),只有該操作符的參數(shù)的匹配是相鄰的并且符合給定順序時(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是一個(gè)表示匹配詞位位置之間的差。<1>和<->相同,而<2>允許剛好一個(gè)其他詞位出現(xiàn)在匹配之間,以此類推。當(dāng)有些詞是停用詞時(shí),phraseto_tsquery函數(shù)利用這個(gè)操作符來(lái)構(gòu)造一個(gè)能夠匹配多詞短語(yǔ)的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'

一種有時(shí)候有用的特殊情況是,<0>可以被用來(lái)要求兩個(gè)匹配同一個(gè)詞的模式。

圓括號(hào)可以被用來(lái)控制tsquery操作符的嵌套。如果沒(méi)有圓括號(hào),|的計(jì)算優(yōu)先級(jí)最低,然后從低到高依次是&、<->、!。

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

配置

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

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

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

為了讓建立自定義文本搜索配置更容易,一個(gè)配置可以從更簡(jiǎn)單的數(shù)據(jù)庫(kù)對(duì)象來(lái)建立。PostgreSQL的文本搜索功能提供了四類配置相關(guān)的數(shù)據(jù)庫(kù)對(duì)象:

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

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


搜索一個(gè)表

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

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

這將還會(huì)找到相關(guān)的詞例如friends和friendly,因?yàn)檫@些都被約減到同一個(gè)正規(guī)化的詞位。

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

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

這個(gè)查詢將使用由default_text_search_config設(shè)置的配置。

一個(gè)更復(fù)雜的例子是選擇 10 個(gè)最近的文檔,要求它們?cè)趖itle或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ù)調(diào)用,它可能需要被用來(lái)查找在這兩個(gè)域之中包含NULL的行。

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

創(chuàng)建索引

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

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

注意這里使用了to_tsvector的雙參數(shù)版本。只有指定了一個(gè)配置名稱的文本搜索函數(shù)可以被用在表達(dá)式索引中。這是因?yàn)樗饕齼?nèi)容必須是沒(méi)有被default_text_search_config影響的。如果它們被影響,索引內(nèi)容可能會(huì)不一致因?yàn)椴煌捻?xiàng)可能包含被使用不同文本搜索配置創(chuàng)建的tsvector,并且沒(méi)有辦法猜測(cè)哪個(gè)是哪個(gè)。也沒(méi)有可能正確地轉(zhuǎn)儲(chǔ)和恢復(fù)這樣的一個(gè)索引。

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

可以建立更復(fù)雜的表達(dá)式索引,在其中配置名被另一個(gè)列指定,例如:

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

這里config_name是pgweb表中的一個(gè)列。這允許在同一個(gè)索引中有混合配置,同時(shí)記錄哪個(gè)配置被用于每一個(gè)索引項(xiàng)。例如,如果文檔集合包含不同語(yǔ)言的文檔,這就可能會(huì)有用。同樣,要使用索引的查詢必須被措辭成匹配,例如WHERE to_tsvector(config_name, body) @@ 'a & b'。

索引甚至可以連接列:

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

另一種方法是創(chuàng)建一個(gè)單獨(dú)的tsvector列來(lái)保存to_tsvector的輸出。若要使此列與其源數(shù)據(jù)保持自動(dòng)更新,用存儲(chǔ)生成的列。這個(gè)例子是title和body的連接,使用coalesce來(lái)保證當(dāng)其他域?yàn)镹ULL時(shí)一個(gè)域仍然能留在索引中:

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

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

CREATE INDEX textsearch_idx ON pgweb USING GIN(textsearchable_index_col);

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

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

單獨(dú)列方法相對(duì)于表達(dá)式索引的一個(gè)優(yōu)勢(shì)在于,它不必為了利用索引而在查詢中顯式地指定文本搜索配置。如上述例子所示,查詢可以依賴default_text_search_config。另一個(gè)優(yōu)勢(shì)是搜索將會(huì)更快,因?yàn)樗槐刂刈鰐o_tsvector調(diào)用來(lái)驗(yàn)證索引匹配(在使用 GiST 索引時(shí)這一點(diǎn)比使用 GIN 索引時(shí)更重要;)。表達(dá)式索引方法更容易建立,但是它要求更少的磁盤(pán)空間,因?yàn)閠svector表示沒(méi)有被顯式地存儲(chǔ)下來(lái)。


解析文檔

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

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

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

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

在上面這個(gè)例子中我們看到,作為結(jié)果的tsvector不包含詞a、on或it,詞rats變成了rat,并且標(biāo)點(diǎn)符號(hào)-被忽略了。

to_tsvector函數(shù)在內(nèi)部調(diào)用了一個(gè)解析器,它把文檔文本分解成記號(hào)并且為每一種記號(hào)分配一個(gè)類型。對(duì)于每一個(gè)記號(hào),會(huì)去查詢一個(gè)詞典列表,該列表會(huì)根據(jù)記號(hào)的類型而變化。第一個(gè)識(shí)別記號(hào)的詞典產(chǎn)生一個(gè)或多個(gè)正規(guī)化的詞位來(lái)表示該記號(hào)。例如,rats變成rat是因?yàn)橐粋€(gè)詞典識(shí)別到該詞rats是rat的復(fù)數(shù)形式。一些詞會(huì)被識(shí)別為停用詞,這將導(dǎo)致它們被忽略,因?yàn)樗鼈兂霈F(xiàn)得太頻繁以至于在搜索中起不到作用。在我們的例子中有a、on和it是停用詞。如果在列表中沒(méi)有詞典能識(shí)別該記號(hào),那它將也會(huì)被忽略。在這個(gè)例子中標(biāo)點(diǎn)符號(hào)-就屬于這種情況,因?yàn)槭聦?shí)上沒(méi)有詞典會(huì)給它分配記號(hào)類型(空間符號(hào)),即空間記號(hào)不會(huì)被索引。對(duì)于解析器、詞典以及要索引哪些記號(hào)類型是由所選擇的文本搜索配置決定的。可以在同一個(gè)數(shù)據(jù)庫(kù)中有多種不同的配置,并且有用于很多種語(yǔ)言的預(yù)定義配置。在我們的例子中,我們使用用于英語(yǔ)的默認(rèn)配置english。

函數(shù)setweight可以被用來(lái)對(duì)tsvector中的項(xiàng)標(biāo)注一個(gè)給定的權(quán)重,這里一個(gè)權(quán)重可以是四個(gè)字母之一:A、B、C或D。這通常被用來(lái)標(biāo)記來(lái)自文檔不同部分的項(xiàng),例如標(biāo)題對(duì)正文。稍后,這種信息可以被用來(lái)排名搜索結(jié)果。

因?yàn)閠o_tsvector(NULL) 將返回NULL,不論何時(shí)一個(gè)域可能為空時(shí),我們推薦使用coalesce。下面是我們推薦的從一個(gè)結(jié)構(gòu)化文檔創(chuàng)建一個(gè)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標(biāo)注每一個(gè)詞位的來(lái)源,并且接著將標(biāo)注過(guò)的tsvector值用tsvector連接操作符||合并在一起。

解析查詢

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

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

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

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

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

SELECT to_tsquery('english', 'Fat | Rats:AB');
    to_tsquery    
------------------
 'fat' | 'rat':AB

同樣,*可以被附加到一個(gè)詞位來(lái)指定前綴匹配:

SELECT to_tsquery('supern:*A & star:A*B');
        to_tsquery        
--------------------------
 'supern':*A & 'star':*AB

這樣一個(gè)詞位將匹配一個(gè)tsvector中的任意以給定字符串開(kāi)頭的詞。

to_tsquery也能夠接受單引號(hào)短語(yǔ)。當(dāng)配置包括一個(gè)會(huì)在這種短語(yǔ)上觸發(fā)的分類詞典時(shí)就是它的主要用處。在下面的例子中,一個(gè)分類詞典含規(guī)則supernovae stars : sn:

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

在沒(méi)有引號(hào)時(shí),to_tsquery將為那些沒(méi)有被 AND、OR 或者 FOLLOWED BY 操作符分隔的記號(hào)產(chǎn)生一個(gè)語(yǔ)法錯(cuò)誤。

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

plainto_tsquery將未格式化的文本querytext轉(zhuǎn)換成一個(gè)tsquery值。該文本被解析并被正規(guī)化,很像to_tsvector,然后&(AND)布爾操作符被插入到留下來(lái)的詞之間。

例子:

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

注意plainto_tsquery不會(huì)識(shí)其輸入中的tsquery操作符、權(quán)重標(biāo)簽或前綴匹配標(biāo)簽:

SELECT plainto_tsquery('english', 'The Fat & Rats:C');
   plainto_tsquery   
---------------------
 'fat' & 'rat' & 'c'

這里,所有輸入的標(biāo)點(diǎn)都被作為空間符號(hào)并且丟棄。

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

phraseto_tsquery的行為很像plainto_tsquery,不過(guò)前者會(huì)在留下來(lái)的詞之間插入<->(FOLLOWED BY)操作符而不是&(AND)操作符。還有,停用詞也不是簡(jiǎn)單地丟棄掉,而是通過(guò)插入<N>操作符(而不是<->操作符)來(lái)解釋。在搜索準(zhǔn)確的詞位序列時(shí)這個(gè)函數(shù)很有用,因?yàn)?FOLLOWED BY 操作符不只是檢查所有詞位的存在性,還會(huì)檢查詞位的順序。

例子:

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

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

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使用一種可供選擇的語(yǔ)法從querytext創(chuàng)建一個(gè)tsquery值,這種語(yǔ)法中簡(jiǎn)單的未格式化文本是一個(gè)有效的查詢。和plainto_tsquery以及phraseto_tsquery不同,它還識(shí)別特定的操作符。此外,這個(gè)函數(shù)絕不會(huì)報(bào)出語(yǔ)法錯(cuò)誤,這就可以把原始的用戶提供的輸入用于搜索。支持下列語(yǔ)法:

  • 無(wú)引號(hào)文本:不在引號(hào)中的文本將被轉(zhuǎn)換成由&操作符分隔的詞,就像被plainto_tsquery處理過(guò)那樣。
  • "引號(hào)文本":在引號(hào)中的文本將被轉(zhuǎn)換成由<->操作符分隔的詞,就像被phraseto_tsquery處理過(guò)那樣。
  • OR:邏輯或?qū)⒈晦D(zhuǎn)換成|操作符。
  • -:邏輯非操作符,被轉(zhuǎn)換成!操作符。

示例:

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)

排名搜索結(jié)果

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

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

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

基于向量的匹配詞位的頻率來(lái)排名向量。

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

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

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

對(duì)這兩個(gè)函數(shù),可選的權(quán)重參數(shù)提供了為詞實(shí)例賦予更多或更少權(quán)重的能力,這種能力是依據(jù)它們被標(biāo)注的情況的。權(quán)重?cái)?shù)組指定每一類詞應(yīng)該得到多重的權(quán)重,按照如下的順序:

{D-權(quán)重, C-權(quán)重, B-權(quán)重, A-權(quán)重}

如果沒(méi)有提供權(quán)重,那么將使用這些默認(rèn)值:

{0.1, 0.2, 0.4, 1.0}

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

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

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

如果多于一個(gè)標(biāo)志位被指定,轉(zhuǎn)換將根據(jù)列出的順序被應(yīng)用。

值得注意的是排名函數(shù)并不使用任何全局信息,因此它不可能按照某些時(shí)候期望地產(chǎn)生一個(gè)公平的正規(guī)化,從 1% 或 100%。正規(guī)化選項(xiàng) 32 (rank/(rank+1))可以被應(yīng)用來(lái)縮放所有的排名到范圍零到一,但是當(dāng)然這只是一個(gè)外觀上的改變;它不會(huì)影響搜索結(jié)果的順序。

這里是一個(gè)例子,它只選擇十個(gè)最高排名的匹配:

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

排名可能會(huì)非常昂貴,因?yàn)樗蟛樵兠恳粋€(gè)匹配文檔的tsvector,這可能會(huì)涉及很多I/O因而很慢。不幸的是,這幾乎不可能避免,因?yàn)閷?shí)際查詢常常導(dǎo)致巨大數(shù)目的匹配。

加亮結(jié)果

要表示搜索結(jié)果,理想的方式是顯示每一個(gè)文檔的一個(gè)部分并且顯示它是怎樣與查詢相關(guān)的。通常,搜索引擎顯示文檔片段時(shí)會(huì)對(duì)其中的搜索術(shù)語(yǔ)進(jìn)行標(biāo)記。PostgreSQL提供了一個(gè)函數(shù)ts_headline來(lái)實(shí)現(xiàn)這個(gè)功能。

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

ts_headline接受一個(gè)文檔和一個(gè)查詢,并且從該文檔返回一個(gè)引用,在其中來(lái)自查詢的術(shù)語(yǔ)會(huì)被加亮。被用來(lái)解析該文檔的配置可以用config指定;如果config被忽略,將會(huì)使用default_text_search_config配置。

如果一個(gè)options字符串被指定,它必須由一個(gè)逗號(hào)分隔的列表組成,列表中是一個(gè)或多個(gè)option=value對(duì)??捎玫倪x項(xiàng)是:

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

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

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使用原始文檔,而不是一個(gè)tsvector摘要,因此它可能很慢并且應(yīng)該被小心使用。


0 人點(diǎn)贊