支援的版本:目前 (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。 如果清單中沒有字典識別該語彙單元,那麼它也會被忽略。 在這個例子中,標點符號 - 就發生了這種情況,因為事實上沒有為其語彙單元類型(空格符號)分配任何字典,這表示空格語彙單元永遠不會被編入索引。 剖析器、字典以及要編入索引的語彙單元類型的選擇由選定的文字搜尋配置決定(第 12.7 節)。 可以在同一個資料庫中擁有多個不同的配置,並且預先定義的配置可用於各種語言。 在我們的例子中,我們使用了英語的預設配置 english

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

因為 to_tsvector(NULL) 將傳回 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 組成,這些 token 以 tsquery 運算子 & (AND)、| (OR)、! (NOT) 和 <-> (FOLLOWED BY) 分隔,並可以使用括號分組。換句話說,to_tsquery 的輸入必須已經遵循 tsquery 輸入的一般規則,如第 8.11.2 節所述。不同之處在於,基本 tsquery 輸入按原樣採用 token,而 to_tsquery 使用指定的或預設的組態將每個 token 正規化為 lexeme,並根據組態丟棄任何停用詞。例如

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) 運算子。此外,停用詞不會被簡單地丟棄,而是透過插入 <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 在 1999 年《Information Processing and Management》期刊中的論文「Relevance Ranking for One to Three Term Queries」中所述。涵蓋密度與 ts_rank 排名相似,不同之處在於它會考慮匹配 lexeme 彼此之間的鄰近性。

此函式需要 lexeme 位置資訊才能執行其計算。因此,它會忽略 tsvector 中的任何剝離的 lexeme。如果輸入中沒有未剝離的 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)) 可以應用於將所有排名縮放到 0 到 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 配對的列表。可用的選項包括:

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

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

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

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

  • StartSel, StopSel (字串): 用於分隔文件中出現的查詢詞的字串,以將它們與其他摘錄詞彙區分開來。預設值為 <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 摘要,因此速度可能會很慢,應謹慎使用。

提交更正

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