一文掌握GaussDB(DWS) SQL進階技能:全文檢索


摘要:本文簡要介紹了GaussDB(DWS)全文檢索的原理和使用方法。

全文檢索(Text search)顧名思義,就是在給定的文檔中查找指定模式(pattern)的過程。GaussDB(DWS)支持對表格中文本類型的字段及字段的組合做全文檢索,找出能匹配給定模式的文本,並以用戶期望的方式將匹配結果呈現出來。

本文結合筆者的經驗和思考,對GaussDB(DWS)的全文檢索功能作簡要介紹,希望能對讀者有所幫助。

1. 預處理

在指定的文檔中查找一個模式有很多種辦法,例如可以用grep命令搜索一個正則表達式。理論上,對數據庫中的文本字段也可以用類似grep的方式來檢索模式,GaussDB(DWS)中就可以通過關鍵字“LIKE”或操作符“~”來匹配字符串。但這樣做有很多問題。首先對每段文本都要掃描,效率比較低,難以衡量“匹配度”或“相關度”。而且只能機械地匹配字符串,缺少對語法語義的分析能力,例如對英語中的名詞復數,動詞的時態變換等難以自動地識別和匹配,對於由自然語言構成的文本無法獲得令人滿意的檢索結果。

GaussDB(DWS)采用類似搜索引擎的方式來進行全文檢索。首先對給定的文本和模式做預處理,包括從一段文本中提取出單詞或詞組,去掉對檢索無用的停用詞(stop word),對變形后的單詞做標准化等等,使之變為適合檢索的形式再作匹配。

GaussDB(DWS)中,原始的文檔和搜索條件都用文本(text)表示,或者說,用字符串表示。經過預處理后的文檔變為tsvector類型,通過函數to_tsvector來實現這一轉換。例如,

postgres=# select to_tsvector('a fat cat ate fat rats');
            to_tsvector           
-----------------------------------
 'ate':4 'cat':3 'fat':2,5 'rat':6
(1 row)

觀察上面輸出的tsvector類型,可以看到to_tsvector的效果:

  • 首先各個單詞被摘取出來,其位置用整數標識出來,例如“fat”位於原始句子中的第2和第5個詞的位置。
  • 此外,“a”這個詞太常見了,幾乎每個文檔里都會出現,對於檢索到有用的信息幾乎沒有幫助。套用香農理論,一個詞出現的概率越大,其包含的信息量越小。像“a”,“the”這種單詞幾乎不攜帶任何信息,所以被當做停用詞(stop word)去掉了。注意這並沒有影響其他詞的位置編號,“fat”的位置仍然是2和5,而不是1和4。
  • 另外,復數形式的“rats”被換成了單數形式“rat”。這個操作被稱為標准化(Normalize),主要是針對西文中單詞在不同語境中會發生的變形,去掉后綴保留詞根的一種操作。其意義在於簡化自然語言的檢索,例如檢索“rat”時可以將包含“rat”和“rats”的文檔都檢索出來。被標准化后得到的單詞稱為詞位(lexeme),比如“rat”。而原始的單詞被稱為語言符號(token)。

將一個文檔轉換成tsvector形式有很多好處。例如,可以方便地創建索引,提高檢索的速度和效率,當文檔數量巨大時,通過索引來檢索關鍵字比grep這種全文掃描匹配要快得多。再比如,可以對不同關鍵字按重要程度分配不同的權重,方便對檢索結果進行排序,找出相關度最高的文檔等等。

經過預處理后的檢索條件被轉換成tsquery類型,可通過to_tsquery函數實現。例如,

postgres=# select to_tsquery('a & cats & rat');
  to_tsquery  
---------------
 'cat' & 'rat'
(1 row)

從上面的例子可以看到:

  • 跟to_tsvector類似,to_tsquery也會對輸入文本做去掉停用詞、標准化等操作,例如去掉了“a”,把“cats”變成“cat”等。
  • 輸入的檢索條件本身必須用與(&)、或(|)、非(!)操作符連接,例如下面的語句會報錯
postgres=# select to_tsquery('cats rat');
ERROR:  syntax error in tsquery: "cats rat"
CONTEXT:  referenced column: to_tsquery

但plainto_tsquery沒有這個限制。plainto_tsquery會把輸入的單詞變成“與”條件:

postgres=# select plainto_tsquery('cats rat');
 plainto_tsquery
-----------------
 'cat' & 'rat'
(1 row)
postgres=# select plainto_tsquery('cats,rat');
 plainto_tsquery
-----------------
 'cat' & 'rat'
(1 row)

除了用函數之外,還可以用強制類型轉換的方式將一個字符串轉換成tsvector或tsquery類型,例如

postgres=# select 'fat cats sat on a mat and ate a fat rat'::tsvector;
                      tsvector                      
-----------------------------------------------------
 'a' 'and' 'ate' 'cats' 'fat' 'mat' 'on' 'rat' 'sat'
(1 row)
postgres=# select 'a & fat & rats'::tsquery;
       tsquery       
----------------------
 'a' & 'fat' & 'rats'
(1 row)

跟函數的區別是強制類型轉換不會去掉停用詞,也不會作標准化,且對於tsvector類型不會記錄詞的位置。

2. 模式匹配

把輸入文檔和檢索條件轉換成tsvector和tsquery之后,就可以進行模式匹配了。GaussDB(DWS)中使用“@@”操作符來進行模式匹配,成功返回True,失敗返回false。

例如創建如下表格,

postgres=# create table post(
postgres(# id bigint,
postgres(# author name,
postgres(# title text,
postgres(# body text);
CREATE TABLE
-- insert some tuples

然后想檢索body中含有“physics”或“math”的帖子標題,可以用如下的語句來查詢:

postgres=# select title from post where to_tsvector(body) @@ to_tsquery('physics | math');
            title           
-----------------------------
 The most popular math books

也可以將多個字段組合起來查詢:

postgres=# select title from post where to_tsvector(title || ' ' || body) @@ to_tsquery('physics | math');
            title           
-----------------------------
 The most popular math books
(1 row)

注意不同的查詢方式可能產生不同的結果。例如下面的匹配不成功,因為::tsquery沒對檢索條件做標准化,前面的tsvector里找不到“cats”這個詞:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cats & rat'::tsquery;
 ?column?
----------
 f
(1 row)

而同樣的文檔和檢索條件,下面的匹配能成功,因為to_tsquery會把“cats”變成“cat”:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ to_tsquery('cats & rat');
 ?column?
----------
 t
(1 row)

類似地,下面的匹配不成功,因為to_tsvector會把停用詞a去掉:

postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cat & rat & a'::tsquery;
 ?column?
----------
 f
(1 row)

而下面的能成功,因為::tsvector保留了所有詞:

postgres=# select 'a fat cat ate fat rats'::tsvector @@ 'cat & rat & a'::tsquery;
 ?column?
----------
 f
(1 row)

所以應根據需要選擇合適的檢索方式。

此外,@@操作符可以對輸入的text做隱式類型轉換,例如,

postgres=# select title from post where body @@ 'physics | math';
 title
-------
(0 rows)

准確來講,text@@text相當於to_tsvector(text) @@ plainto_tsquery(text),因此上面的匹配不成功,因為plainto_tsquery會把或條件'physics | math'變成與條件'physic' & 'math'。使用時要格外小心。

3. 創建和使用索引

前文提到,逐個掃描表中的文本字段緩慢低效,而索引查找能夠提高檢索的速度和效率。GaussDB(DWS)支持用通用倒排索引GIN(Generalized Inverted Index)進行全文檢索。GIN是搜索引擎中常用的一種索引,其主要原理是通過關鍵字反過來查找所在的文檔,從而提高查詢效率。可通過以下語句在text類型的字段上創建GIN索引:

postgres=# create index post_body_idx_1 on post using gin(to_tsvector('english', body));
CREATE INDEX

注意這里必須使用to_tsvector函數生成tsvector,不能使用強制或隱式類型轉換。而且這里用到的to_tsvector函數比前一節多了一個參數’english’,這個參數是用來指定文本搜索配置(Text search Configuration)的。關於文本搜索配置將在下一節介紹。不同的配置計算出來的tsvector不同,生成的索引自然也不同,所以這里必須明確指定,而且在查詢的時候只有配置和字段都與索引定義一致才能通過索引查找。例如下面的查詢中,前一個可以通過post_body_idx_1來檢索,后一個找不到對應的索引,只能通過全表掃描檢索。

postgres=# explain select title from post where to_tsvector('english', body) @@ to_tsquery('physics | math');
                                             QUERY PLAN                                             
-----------------------------------------------------------------------------------------------------
  id |            operation            | E-rows | E-width | E-costs
 ----+---------------------------------+--------+---------+---------
   1 | ->  Streaming (type: GATHER)    |      1 |      32 | 42.02  
   2 |    ->  Bitmap Heap Scan on post |      1 |      32 | 16.02  
   3 |       ->  Bitmap Index Scan     |      1 |       0 | 12.00  
postgres=# explain select title from post where to_tsvector('french', body) @@ to_tsquery('physics | math');
                                          QUERY PLAN                                         
----------------------------------------------------------------------------------------------
  id |          operation           | E-rows | E-width |     E-costs     
 ----+------------------------------+--------+---------+------------------
   1 | ->  Streaming (type: GATHER) |      1 |      32 | 1000000002360.50
   2 |    ->  Seq Scan on post      |      1 |      32 | 1000000002334.50

4. 全文檢索配置(Text search Configuration)

這一節談談GaussDB(DWS)如何對文檔做預處理,或者說,to_tsvector是如何工作的。

文檔預處理大體上分如下三步進行:

  • 第一步,將文本中的單詞或詞組一個一個提取出來。這項工作由解析器(Parser)或稱分詞Segmentation)器來進行。完成后文檔變成一系列token。
  • 第二步,對上一步得到的token做標准化,包括依據指定的規則去掉前后綴,轉換同義詞,去掉停用詞等等,從而得到一個個詞位(lexeme)。這一步操作依據詞典(Dictionary)來進行,也就是說,詞典定義了標准化的規則。
  • 最后,記錄各個詞位的位置(和權重),從而得到tsvector。

從上面的描述可以看出,如果給定了解析器和詞典,那么文檔預處理的規則也就確定了。在GaussDB(DWS)中,這一整套文檔預處理的規則稱為全文檢索配置(Text search Configuration)。全文檢索配置決定了匹配的結果和質量。

如下圖所示,一個全文檢索配置由一個解析器和一組詞典組成。輸入文檔首先被解析器分解成token,然后對每個token逐個詞典查找,如果在某個詞典中找到這個token,就按照該詞典的規則對其做Normalize。有的詞典做完Normalize后會將該token標記為“已處理”,這樣后面的字典就不會再處理了。有的詞典做完Normalize后將其輸出為新的token交給后面的詞典處理,這樣的詞典稱為“過濾型”詞典。

圖1 文檔預處理過程

配置使用的解析器在創建配置的時候指定,且不可修改,例如,

postgres=# create text search configuration mytsconf (parser = default);
CREATE TEXT SEARCH CONFIGURATION

GaussDB(DWS)內置了4種解析器,目前不支持自定義解析器。

postgres=# select prsname from pg_ts_parser;
 prsname 
----------
 default
 ngram
 pound
 zhparser
(4 rows)

詞典則通過ALTER TEXT SEARCH CONFIGURATION命令來指定,例如

postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION

 

postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION

上面語句中的“asciiword”是一種token類型。解析器會對分解出的token做分類,不同的解析器分類方式不同,可通過ts_token_type函數查看。例如,‘default’解析器將token分為如下23種類型:指定了mytsconf使用english_stem和simple這兩種詞典來對“asciiword”類型的token做標准化。

postgres=# select * from ts_token_type('default');
 tokid |      alias      |               description                
-------+-----------------+------------------------------------------
     1 | asciiword       | Word, all ASCII
     2 | word            | Word, all letters
     3 | numword         | Word, letters and digits
     4 | email           | Email address
     5 | url             | URL
     6 | host            | Host
     7 | sfloat          | Scientific notation
     8 | version         | Version number
     9 | hword_numpart   | Hyphenated word part, letters and digits
    10 | hword_part      | Hyphenated word part, all letters
    11 | hword_asciipart | Hyphenated word part, all ASCII
    12 | blank           | Space symbols
    13 | tag             | XML tag
    14 | protocol        | Protocol head
    15 | numhword        | Hyphenated word, letters and digits
    16 | asciihword      | Hyphenated word, all ASCII
    17 | hword           | Hyphenated word, all letters
    18 | url_path        | URL path
    19 | file            | File or path name
    20 | float           | Decimal notation
    21 | int             | Signed integer
    22 | uint            | Unsigned integer
    23 | entity          | XML entity
(23 rows)

當前數據庫中已有的詞典可以通過系統表pg_ts_dict查詢。

如果指定了配置,系統會按照指定的配置對文檔作預處理,如上一節創建GIN索引的命令。如果沒指定配置,to_tsvector使用default_text_search_config變量指定的默認配置。

postgres=# show default_text_search_config; -- 查看當前默認配置
 default_text_search_config
----------------------------
 pg_catalog.english
(1 row)
postgres=# set default_text_search_config = mytsconf;  -- 設置默認配置
SET
postgres=# show default_text_search_config;
 default_text_search_config
----------------------------
 public.mytsconf
(1 row)
postgres=# reset default_text_search_config;  -- 恢復默認配置
RESET
postgres=# show default_text_search_config;
 default_text_search_config
----------------------------
 pg_catalog.english
(1 row)

注意default_text_search_config是一個session級的變量,只在當前會話中有效。如果想讓默認配置持久生效,可以修改postgresql.conf配置文件中的同名變量,如下圖所示。

修改后需要重啟進程。

總結

GaussDB(DWS)的全文檢索模塊提供了強大的文檔搜索功能。相比於用“LIKE”關鍵字,或 “~”操作符的模式匹配,全文檢索提供了較豐富的語義語法支持,能對自然語言文本做更加智能化的處理。配合恰當的索引,能夠實現對文檔的高效檢索。

本文分享自華為雲社區《GaussDB(DWS) SQL進階之全文檢索》,原文作者:Zhang Jingyao  。

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM