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

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

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

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

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

1. 預處理

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

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

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

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這種全文掃描匹配要快得多。再好比,能夠對不一樣關鍵字按重要程度分配不一樣的權重,方便對檢索結果進行排序,找出相關度最高的文檔等等。post

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

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

指定了mytsconf使用english_stem和simple這兩種詞典來對「asciiword」類型的token作標準化。

上面語句中的「asciiword」是一種token類型。解析器會對分解出的token作分類,不一樣的解析器分類方式不一樣,可經過ts_token_type函數查看。例如,‘default’解析器將token分爲以下23種類型:

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」關鍵字,或 「~」操做符的模式匹配,全文檢索提供了較豐富的語義語法支持,能對天然語言文本作更加智能化的處理。配合恰當的索引,可以實現對文檔的高效檢索。

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

相關文章
相關標籤/搜索