2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 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 將文字文件解析為標記 (tokens),將標記歸約為詞元 (lexemes),並返回一個 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,因此建議在欄位可能為 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 的簡化版本,具有替代語法,類似於 Web 搜尋引擎使用的語法。

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

to_tsqueryquerytext 建立一個 tsquery 值,querytext 必須由 tsquery 運算子 & (AND)、| (OR)、! (NOT) 和 <-> (FOLLOWED BY) 分隔的單個標記組成,並可能使用括號分組。換句話說,to_tsquery 的輸入必須已經遵循 tsquery 輸入的一般規則,如 第 8.11.2 節 所述。區別在於,雖然基本的 tsquery 輸入按原樣接受標記,但 to_tsquery 使用指定的或預設的配置將每個標記規範化為詞元,並丟棄根據配置是停用詞的任何標記。例如:

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 還可以接受帶引號的短語。這主要在配置包含可能根據這些短語觸發的同義詞典時有用。在下面的示例中,同義詞典包含規則 supernovae stars : sn

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

不帶引號的情況下,對於未被 AND、OR 或 FOLLOWED BY 運算子分隔的標記,to_tsquery 將生成語法錯誤。

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> 運算子而不是 <-> 運算子來處理。此函式在搜尋精確的詞元序列時很有用,因為 FOLLOWED BY 運算子會檢查詞元順序,而不僅僅是所有詞元是否存在。

示例:

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 不同,它還識別某些運算子。此外,此函式永遠不會引發語法錯誤,這使得可以使用原始的使用者提供的輸入進行搜尋。支援以下語法:

  • 未加引號的文字:未放在引號內的文字將轉換為用 & 運算子分隔的術語,就像由 plainto_tsquery 處理一樣。

  • "加引號的文字":放在引號內的文字將轉換為用 <-> 運算子分隔的術語,就像由 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

根據匹配詞元頻率對向量進行排名。

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

此函式計算給定文件向量和查詢的 覆蓋密度 排名,如 Clarke、Cormack 和 Tudhope 在期刊《Information Processing and Management》1999 年發表的論文“Relevance Ranking for One to Three Term Queries”中所述。覆蓋密度與 ts_rank 排名類似,但考慮了匹配詞元之間的鄰近性。

此函式需要詞元位置資訊才能執行計算。因此,它會忽略 tsvector 中的任何 已剝離 詞元。如果輸入中沒有未剝離的詞元,則結果將為零。(有關 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 除以 extents 之間的平均調和距離(這僅由 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(字串):當顯示多個片段時,片段將由此字串分隔。預設值為 ...

警告:跨站指令碼(XSS)安全

ts_headline 輸出的內容不能保證可以直接包含在網頁中。當 HighlightAllfalse(預設值)時,會從文件中刪除一些簡單的 XML 標籤,但這不能保證刪除所有 HTML 標記。因此,在處理不受信任的輸入時,這不能有效地防範跨站指令碼(XSS)等攻擊。為了防範此類攻擊,應從輸入文件中刪除所有 HTML 標記,或者在輸出時使用 HTML 清理器。

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

在非基於片段的標題生成中,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 摘要,因此它可能速度較慢,應謹慎使用。

提交更正

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