支援的版本: 目前 (17) / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3

12.3. 控制文字搜尋 #

若要實作全文搜尋,必須要有函數從文件建立 tsvector,以及從使用者查詢建立 tsquery。此外,我們需要以有用的順序傳回結果,因此我們需要一個函數來比較文件與查詢的相關性。能夠漂亮地顯示結果也很重要。PostgreSQL 提供所有這些函數的支援。

12.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 不包含單字 aonit,單字 rats 變成 rat,且標點符號 - 被忽略。

to_tsvector 函數在內部呼叫剖析器,將文件文字分成權杖,並為每個權杖指定一個類型。對於每個權杖,都會查詢字典清單(第 12.6 節),其中清單可能會因權杖類型而異。第一個識別權杖的字典會發出一個或多個正規化的語素來表示該權杖。例如,rats 變成 rat,因為其中一個字典識別到單字 ratsrat 的複數形式。有些單字被識別為停用詞第 12.6.1 節),這會導致它們被忽略,因為它們出現的頻率太高而無法用於搜尋。在我們的範例中,這些是 aonit。如果清單中沒有字典識別該權杖,則也會忽略它。在此範例中,標點符號 - 發生了這種情況,因為實際上沒有為其權杖類型 (Space symbols) 分配任何字典,這表示永遠不會索引空格權杖。剖析器、字典以及要索引的權杖類型的選擇由選取的文字搜尋設定決定(第 12.7 節)。同一個資料庫中可以有多個不同的設定,並且為各種語言提供預定義的設定。在我們的範例中,我們使用了英文的預設設定 english

函數 setweight 可用於使用給定的權重標記 tsvector 的項目,其中權重是字母 ABCD 之一。這通常用於標記來自文件中不同部分的項目,例如標題與內文。稍後,此資訊可用於排序搜尋結果。

因為 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 值。(第 12.4.1 節 提供了有關這些操作的詳細資訊。)

12.3.2. 解析查詢 #

PostgreSQL 提供了函數 to_tsqueryplainto_tsqueryphraseto_tsquerywebsearch_to_tsquery,用於將查詢轉換為 tsquery 資料類型。to_tsquery 提供的功能比 plainto_tsqueryphraseto_tsquery 更多,但對其輸入的容錯性較差。websearch_to_tsqueryto_tsquery 的簡化版本,具有另一種語法,類似於網路搜尋引擎所使用的語法。

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

to_tsqueryquerytext 建立一個 tsquery 值,該值必須由單一 Token 組成,並以 tsquery 運算子 & (AND)、| (OR)、! (NOT) 和 <-> (FOLLOWED BY) 分隔,並可以使用括號進行分組。換句話說,to_tsquery 的輸入必須已經遵循 tsquery 輸入的一般規則,如 第 8.11.2 節所述。不同之處在於,基本的 tsquery 輸入將 Token 按字面意義採用,而 to_tsquery 使用指定的或預設的組態,將每個 Token 正規化為一個語位 (lexeme),並根據組態丟棄任何停止詞 (stop word) Token。例如:

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

與基本的 tsquery 輸入一樣,權重可以附加到每個語位 (lexeme) 上,以限制它只匹配那些權重的 tsvector 語位 (lexeme)。例如:

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

此外,* 可以附加到語位 (lexeme) 上,以指定前綴匹配:

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

這樣的語位 (lexeme) 將會匹配 tsvector 中以給定字串開頭的任何單字。

to_tsquery 也可以接受單引號括住的詞組。當組態包含一個詞庫字典時,這特別有用,該詞庫字典可能會觸發這些詞組。在下面的範例中,詞庫包含規則 supernovae stars : sn

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

如果沒有引號,to_tsquery 會為未被 AND、OR 或 FOLLOWED BY 運算子分隔的 Token 產生語法錯誤。

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

plainto_tsquery 將未格式化的文字 querytext 轉換為 tsquery 值。文字會被解析和正規化,就像 to_tsvector 一樣,然後在剩餘的單字之間插入 & (AND) tsquery 運算子。

範例:

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) 運算子。此外,停止詞 (stop word) 不會被簡單地丟棄,而是透過插入 <N> 運算子而不是 <-> 運算子來考慮。此函數在搜尋精確的語位 (lexeme) 序列時非常有用,因為 FOLLOWED BY 運算子會檢查語位 (lexeme) 的順序,而不僅僅是所有語位 (lexeme) 的存在。

範例:

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'
websearch_to_tsquery([ config regconfig, ] querytext text) returns tsquery

websearch_to_tsquery 使用另一種語法,從 querytext 建立一個 tsquery 值,在這種語法中,簡單的未格式化文字是有效的查詢。與 plainto_tsqueryphraseto_tsquery 不同,它也會辨識某些運算子。此外,此函數永遠不會引發語法錯誤,因此可以使用原始的由使用者提供的輸入來進行搜尋。支援以下語法:

  • unquoted text:引號外的文字將被轉換為以 & 運算子分隔的詞語,就像由 plainto_tsquery 處理一樣。

  • "quoted text":引號內的文字將被轉換為以 <-> 運算子分隔的詞語,就像由 phraseto_tsquery 處理一樣。

  • OR:單字 or 將被轉換為 | 運算子。

  • -:破折號將被轉換為 ! 運算子。

其他的標點符號會被忽略。因此,與 plainto_tsqueryphraseto_tsquery 類似,websearch_to_tsquery 函數不會辨識其輸入中的 tsquery 運算子、權重標籤或前綴匹配標籤。

範例:

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. 搜尋結果排名 #

排名旨在衡量文件與特定查詢的相關程度,以便在有許多匹配項時,可以首先顯示最相關的文件。PostgreSQL 提供了兩個預定義的排名函數,它們會考慮詞彙、鄰近性和結構資訊;也就是說,它們會考慮查詢詞語在文件中出現的頻率、詞語在文件中彼此之間的距離,以及詞語出現的文件部分的重要性。然而,相關性的概念是模糊的,並且與應用程式高度相關。不同的應用程式可能需要額外的資訊來進行排名,例如,文件修改時間。內建的排名函數僅是範例。您可以編寫自己的排名函數,和/或將其結果與其他因素結合,以符合您的特定需求。

目前可用的兩個排名函數是:

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

根據它們匹配的語位 (lexeme) 的頻率對向量進行排名。

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

此函數計算給定文件向量和查詢的覆蓋密度排名,如 Clarke、Cormack 和 Tudhope 在 "Information Processing and Management" 期刊的 "Relevance Ranking for One to Three Term Queries" 中所述。覆蓋密度與 ts_rank 排名類似,不同之處在於它考慮了匹配語位 (lexeme) 彼此之間的鄰近性。

此函數需要語位 (lexeme) 的位置資訊才能執行其計算。因此,它會忽略 tsvector 中的任何stripped語位 (lexeme)。如果輸入中沒有 unstripped 的語位 (lexeme),則結果將為零。(有關 strip 函數和 tsvector 中位置資訊的更多資訊,請參閱第 12.4.1 節。)

對於這兩個函數,可選的 weights 引數能夠根據單字的標記方式,對單字實例進行或多或少的加權。權重陣列指定每個單字類別的加權程度,順序如下:

{D-weight, C-weight, B-weight, A-weight}

如果沒有提供 weights,則使用這些預設值:

{0.1, 0.2, 0.4, 1.0}

通常,權重用於標記文件中特殊區域(如標題或初始摘要)中的單字,以便它們可以被視為比文件中正文中的單字更重要或更不重要。

由於較長的文檔包含查詢詞彙的機率較大,因此考量文檔大小是合理的。例如,一個包含五個搜尋詞彙的百字文檔,可能比一個包含五個搜尋詞彙的千字文檔更相關。這兩個排名函數都有一個整數 normalization 選項,用於指定文檔長度是否以及如何影響其排名。這個整數選項控制多個行為,因此它是一個位元遮罩:您可以使用 | 來指定一個或多個行為(例如,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 限制,因此速度會很慢。不幸的是,這幾乎是不可避免的,因為實際查詢通常會產生大量的匹配項。

12.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 對的列表。可用的選項是

  • MaxWordsMinWords (整數):這些數字決定了要輸出的最長和最短標題。預設值分別為 35 和 15。

  • ShortWord (整數):長度小於或等於此長度的單詞將在標題的開頭和結尾被刪除,除非它們是查詢詞彙。預設值為 3,用於消除常見的英文冠詞。

  • HighlightAll (布林值):如果 true,則整個文檔將用作標題,忽略前面三個參數。預設值為 false

  • MaxFragments (整數):要顯示的文字片段的最大數量。預設值為零,選擇一種非基於片段的標題生成方法。大於零的值選擇基於片段的標題生成(見下文)。

  • StartSelStopSel (字串):用於分隔出現在文檔中的查詢詞彙的字串,以將它們與其他摘錄的詞彙區分開來。預設值為 <b></b>,這可能適用於 HTML 輸出。

  • FragmentDelimiter (字串):當顯示多個片段時,片段將由這個字串分隔。預設值為 ...

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

在非基於片段的標題生成中,ts_headline 會找到給定的 query 的匹配項,並選擇一個來顯示,優先選擇在允許的標題長度內具有更多查詢詞彙的匹配項。在基於片段的標題生成中,ts_headline 會找到查詢匹配項,並將每個匹配項分成不超過 MaxWords 個單詞的 片段,優先選擇具有更多查詢詞彙的片段,並且在可能的情況下 延伸 片段以包含周圍的單詞。因此,當查詢匹配項跨越文檔的大部分,或者當需要顯示多個匹配項時,基於片段的模式更有用。在任一模式下,如果無法識別任何查詢匹配項,則將顯示文檔中前 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 摘要,因此它可能很慢,應謹慎使用。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表格報告文件問題。