全部產品
Search
文件中心

PolarDB:控制文本搜尋

更新時間:Jul 06, 2024

要實現全文檢索搜尋必須要有一個從文檔建立tsvector以及從使用者查詢建立tsquery的函數。而且我們需要一種有用的順序返回結果,因此我們需要一個函數能夠根據文檔與查詢的相關性比較文檔。還有一點重要的是要能夠很好地顯示結果。本資料庫中對所有這些函數都提供了支援。

解析文檔

本資料庫中提供了函數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函數在內部調用了一個解析器,它把文檔文本分解成記號並且為每一種記號分配一個類型。對於每一個記號,會去查詢一個詞典列表,該列表會根據記號的類型而變化。第一個識別記號的詞典產生一個或多個正規化的詞位來表示該記號。例如,rats變成rat是因為一個詞典識別到該詞ratsrat的複數形式。

一些詞會被識別為停用詞,這將導致它們被忽略,因為它們出現得太頻繁以至於在搜尋中起不到作用。以上樣本中,aonit是停用詞。

如果在列表中沒有詞典能識別該記號,那它將也會被忽略。在這個例子中標點符號-就屬於這種情況,因為事實上沒有詞典會給它分配記號類型(空間符號),即空間記號不會被索引。對於解析器、詞典以及要索引哪些記號類型是由所選擇的文本搜尋配置決定的。可以在同一個資料庫中有多種不同的配置,並且有用於很多種語言的預定義配置。在我們的例子中,我們使用用於英語的預設配置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串連操作符||合并在一起。

解析查詢

本資料庫中提供了函數to_tsqueryplainto_tsqueryphraseto_tsquery以及websearch_to_tsquery用來把一個查詢轉換成tsquery資料類型。to_tsquery提供了比plainto_tsqueryphraseto_tsquery更多的特性,但是它對其輸入要求更加嚴格。websearch_to_tsqueryto_tsquery的一個簡化版本,它使用一種可選擇的文法,類似於 Web 搜尋引擎使用的文法。

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

to_tsqueryquerytext建立一個tsquery值,該值由被tsquery操作符(AND)、|(OR)、!(NOT)和<->(FOLLOWED BY)分隔的單個記號組成。這些操作符可以使用圓括弧分組。換句話說,to_tsquery的輸入必須已經遵循tsquery輸入的一般規則。區別在於基本的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'

在沒有引號時,to_tsquery將為那些沒有被 AND、OR 或者 FOLLOWED BY 操作符分隔的記號產生一個語法錯誤。

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

plainto_tsquery將未格式化的文本querytext轉換成一個tsquery值。該文本被解析並被正規化,很像to_tsvector,然後&(AND)布爾操作符被插入到留下來的詞之間。例子:

    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_tsquery以及phraseto_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)

排名搜尋結果

排名處理嘗試度量文檔和一個特定查詢的接近程度,這樣當有很多匹配時最相關的那些可以被先顯示。本資料庫中提供了兩種預定義的次序函數,它們考慮詞法、臨近性和結構資訊;即,它們考慮查詢詞在文檔中出現得有多頻繁,文檔中的詞有多接近,以及詞出現的文檔部分有多重要。不過,相關性的概念是模糊的並且與應用非常相關。不同的應用可能要求額外的資訊用於排名,例如,文檔修改時間。內建的次序函數只是例子。你可以編寫你自己的次序函數和/或把它們的結果與附加因素整合在一起來適應你的特定需求。

目前可用的兩種次序函數是:

  • 基於向量的匹配詞位的頻率來排名向量。

    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

    覆蓋密度類似於ts_rank排名,不過它會考慮匹配詞位相互之間的接近度。這個函數要求詞位的位置資訊來執行其計算。因此它會忽略tsvector中任何“被剝離的”詞位。如果在輸入中有未被剝離的詞位,結果將會是零。

對這兩個函數,可選的weights參數提供了為詞執行個體賦予更多或更少權重的能力,這種能力是依據它們被標註的情況的。權重數組指定每一類詞應該得到多重的權重,按照如下的順序:

    {D-權重, C-權重, B-權重, A-權重}

如果沒有提供權重,那麼將使用這些預設值:

    {0.1, 0.2, 0.4, 1.0}

通常權重被用來標記來自文檔特別地區的詞,如標題或一個初始的摘要,這樣它們可以被認為比來自文檔本文的詞更重要或更不重要。由於一個較長的文檔有更多的機會包含一個查詢術語,因此考慮文檔的尺寸是合理的,例如一個一百個詞的文檔中有一個搜尋字詞的五個執行個體而零一個一千個詞的文檔中有該搜尋字詞的五個執行個體,則前者比後者更相關。兩種次序函數都採用一個整數正規化選項,它指定文檔長度是否影響其排名以及如何影響。該整數選項控制多個行為,因此它是一個位元遮罩:你可以使用|指定一個或多個行為(例如,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因而很慢。不幸的是,這幾乎不可能避免,因為實際查詢常常導致巨大數目的匹配。

高亮結果

要表示搜尋結果,理想的方式是顯示每一個文檔的一個部分並且顯示它是怎樣與查詢相關的。通常,搜尋引擎顯示文檔片段時會對其中的搜尋術語進行標記。本資料庫中提供了一個函數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(strings):用於分隔文檔中出現的查詢詞的字串,以將它們與其他摘錄的單詞區分開來。 預設值為“<b>” 和 “</b>”,它可以適用於 HTML 輸出。

  • FragmentDelimiter (string):當顯示多個片段時,片段會被這個字串分隔。預設值為“...”。

這些選項名稱不區分大小寫。如果字串值包含空格或逗號,則必須用雙引號引起來。在基於非片段的標題產生中,ts_headline為給定的query尋找匹配項,並選擇一個要顯示的匹配項,優先選擇在允許標題長度內具有更多查詢詞的匹配項。 在基於片段的標題產生中,ts_headline定位查詢匹配項,並將每個匹配項拆分為“fragments”,每個匹配項不超過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摘要,因此它可能很慢並且應該被小心使用。