PostgreSQL 控制文本搜索

2021-08-27 14:40 更新
12.3.1. 解析文檔
12.3.2. 解析查詢
12.3.3. 排名搜索結(jié)果
12.3.4. 加亮結(jié)果

要實現(xiàn)全文搜索必須要有一個從文檔創(chuàng)建tsvector以及從用戶查詢創(chuàng)建tsquery的函數(shù)。而且我們需要一種有用的順序返回結(jié)果,因此我們需要一個函數(shù)能夠根據(jù)文檔與查詢的相關(guān)性比較文檔。還有一點重要的是要能夠很好地顯示結(jié)果。PostgreSQL對所有這些函數(shù)都提供了支持。

12.3.1. 解析文檔

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

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

to_tsvector把一個文本文檔解析成記號,把記號縮減成詞位,并且返回一個tsvector,它列出了詞位以及詞位在文檔中的位置。文檔被根據(jù)指定的或默認(rè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

在上面這個例子中我們看到,作為結(jié)果的tsvector不包含詞aonit,詞rats變成了rat,并且標(biāo)點符號-被忽略了。

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

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

因為to_tsvector(NULL) 將返回NULL,不論何時一個域可能為空時,我們推薦使用coalesce。下面是我們推薦的從一個結(jié)構(gòu)化文檔創(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標(biāo)注每一個詞位的來源,并且接著將標(biāo)注過的tsvector值用tsvector連接操作符||合并在一起(第 12.4.1 節(jié)給出了關(guān)于這些操作符的細(xì)節(jié))。

12.3.2. 解析查詢

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

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

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

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

和在基本tsquery輸入中一樣,權(quán)重可以被附加到每一個詞位來限制它只匹配屬于那些權(quán)重的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也能夠接受單引號短語。當(dāng)配置包括一個會在這種短語上觸發(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轉(zhuǎn)換成一個tsquery值。該文本被解析并被正規(guī)化,很像to_tsvector,然后&(AND)布爾操作符被插入到留下來的詞之間。

例子:

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

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

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

這里,所有輸入的標(biāo)點都被丟棄。

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

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

例子:

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

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

  • 無引號文本:不在引號中的文本將被轉(zhuǎn)換成由&操作符分隔的詞,就像被plainto_tsquery處理過那樣。

  • "引號文本":在引號中的文本將被轉(zhuǎn)換成由<->操作符分隔的詞,就像被phraseto_tsquery處理過那樣。

  • ORor將轉(zhuǎn)換為|運算符。

  • -:破折號將轉(zhuǎn)換為 ! 運算符。

忽略其他標(biāo)點符號。因此,與 plainto_tsqueryphraseto_tsquery 一樣,websearch_to_tsquery函數(shù)在其輸入中將不會識別tsquery運算符、權(quán)重標(biāo)簽或前綴匹配標(biāo)簽。

示例:

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)

12.3.3. 排名搜索結(jié)果

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

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

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

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

這個函數(shù)要求詞位的位置信息來執(zhí)行其計算。因此它會忽略tsvector中任何被剝離的詞位。如果在輸入中有未被剝離的詞位,結(jié)果將會是零(strip函數(shù)和tsvector中的位置信息的更多內(nèi)容請見第 12.4.1 節(jié))。

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

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

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

{0.1, 0.2, 0.4, 1.0}

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

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

  • 0(默認(rèn)值)忽略文檔長度

  • 1 用 1 + 文檔長度的對數(shù)除排名

  • 2 用文檔長度除排名

  • 4 用長度之間的平均調(diào)和距離除排名(只被ts_rank_cd實現(xiàn))

  • 8 用文檔中唯一詞的數(shù)量除排名

  • 16 用 1 + 文檔中唯一詞數(shù)量的對數(shù)除排名

  • 32 用排名 + 1 除排名

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

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

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

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

12.3.4. 加亮結(jié)果

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

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

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

如果一個options字符串被指定,它必須由一個逗號分隔的列表組成,列表中是一個或多個option=value對。可用的選項是:

  • MaxWords、MinWords(整數(shù)):這些數(shù)字決定了要輸出的最長和最短標(biāo)題。 默認(rèn)值為 35 和 15。

  • ShortWord(整數(shù)):此長度或更短的單詞將被刪除在標(biāo)題的開頭和結(jié)尾,除非它們是查詢詞。默認(rèn)值為3將刪除常見的英語冠詞。

  • HighlightAll (布爾值):如果 true 將整個文檔用作標(biāo)題,忽略前面三個參數(shù)。 默認(rèn)值為 false

  • MaxFragments(整數(shù)):要顯示的最大文本片段數(shù)。默認(rèn)值為零選擇非基于片段的標(biāo)題生成方法。大于零的值選擇基于片段的標(biāo)題生成(見下文)。

  • StartSelStopSel(strings):用于分隔文檔中出現(xiàn)的查詢詞的字符串,以將它們與其他摘錄的單詞區(qū)分開來。 默認(rèn)值為<b></b>,它可以適用于HTML輸出。

  • FragmentDelimiter (string):當(dāng)顯示多個片段時,片段會被這個字符串分隔。 默認(rèn)值為 ... 。

這些選項名稱不區(qū)分大小寫。 如果字符串值包含空格或逗號,則必須用雙引號引起來。

在基于非片段的標(biāo)題生成中,ts_headline為給定的query查找匹配項,并選擇一個要顯示的匹配項,優(yōu)先選擇在允許標(biāo)題長度內(nèi)具有更多查詢詞的匹配項。 在基于片段的標(biāo)題生成中,ts_headline定位查詢匹配項,并將每個匹配項拆分為fragments,每個匹配項不超過 MaxWords個詞,首選具有更多查詢詞的片段,并且在可能的情況下拉伸片段以包括周圍的詞。 因此,當(dāng)查詢匹配跨越文檔的大部分時,或者當(dāng)需要顯示多個匹配時,基于片段的模式更有用。 在任一模式下,如果無法識別查詢匹配項,則將顯示文檔中前 MinWords 單詞的單個片段。

例如:

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('english', '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',
  'Search terms may occur
many times in a document,
requiring ranking of the search matches to decide which
occurrences to display in the result.',
  to_tsquery('english', 'search & term'),
  'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');
                        ts_headline
------------------------------------------------------------
 <<Search>> <<terms>> may occur                            +
 many times ... ranking of the <<search>> matches to decide

ts_headline使用原始文檔,而不是一個tsvector摘要,因此它可能很慢并且應(yīng)該被小心使用。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號