一文掌握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”关键字,或 “~”操做符的模式匹配,全文检索提供了较丰富的语义语法支持,能对天然语言文本作更加智能化的处理。配合恰当的索引,可以实现对文档的高效检索。

点击关注,第一时间了解华为云新鲜技术~

相关文章
相关标签/搜索