文本搜索字典
Apache Cloudberry 的全文搜索解析器生成的词元(token)会依次传递给一系列字典,用于生成标准化的术语,即“词素”(lexeme)。可使用不同类型的字典,以适应不同语言并按需对词元进行过滤和转换。
本节包含以下子主题:
关于文本搜索字典
字典的作用是过滤掉不应参与搜索的词(即停用词),并将词语标准化,使相同词的不同派生形式能匹配在一起。标准化后的词称为词素(lexeme)。除了提升搜索质量,词的标准化和停用词移除还能减少 tsvector
表示文档时的大小,从而提高性能。标准化未必具备语言学含义,更多取决于应用语义。
一些标准化示例:
- 语言层面:Ispell 字典尝试将输入词还原为标准形式;词干提取(stemmer)字 典会去除词尾;
- URL 标准化:可将等价的链接转换为统一形式,例如:
http://www.pgsql.ru/db/mw/index.html
http://www.pgsql.ru/db/mw/
http://www.pgsql.ru/db/../db/mw/index.html
- 颜色名称可转换为十六进制值,如:
red
,green
,blue
,magenta
→FF0000
,00FF00
,0000FF
,FF00FF
- 如果索引的是数字,可去除小数部分以减少取值范围,例如:
3.14159265359
、3.1415926
和3.14
在只保留两位小数时都会标准化为相同值。
一个字典程序接收词元作为输入,返回以下几种结果之一:
- 如果该词元在字典中已知,返回一个词素数组(注意:一个词元可能产生多个词素);
- 返回一个带有
TSL_FILTER
标志的单个词素,表示将词元替换为一个新的词元,并传递给后续字典(此类字典称为 过滤字典); - 如果该词元是已知的停用词,返回空数组;
- 如果字典无法识别该词元,返回
NULL
。
Apache Cloudberry 为多种语言预定义了字典模板,并允许基于模板创建自定义字典。如果现有模板不适用,也可以创建新的模板,详见 Apache Cloudberry 源码包的 contrib/
目录中的示例。
文本搜索配置将解析器与字典集合组合起来,用于处理解析器返回的不同类型的词元。配置为解析器的每一种词元类型指定一个字典列表。当解析器识别到某个词元时,会按配置顺序依次查询字典:
- 一旦某个字典识别出该词元(即返回非 NULL),就停止继续;
- 如果识别结果是停用词,或所有字典都未能识别该词元,该词元将被丢弃,不再索引或参与搜索;
- 如果某个字典是过滤字典,并返回了一个新词元,则这个新词元会继续传递给后续字典处理。
配置字典列表时的一般规则是:先放最专用、最严格的字典,再放通用字典,最后放最宽松的字典,如 Snowball stemmer 或 simple
字典(它能识别所有词元)。例如,针对天文学搜索(配置名为 astro_en
),可以为 asciiword
类型设置如下字典链:
ALTER TEXT SEARCH CONFIGURATION astro_en
ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;
过滤字典可以放在链中任意位置,但不能放在最后,否则不起作用。它们适合用于对词元进行部分标准化,简化后续字典的处理任务。例如,可用过滤字典去除字符中的重音符号,类似于 unaccent
模块的功能。
停用词(Stop words)
停用词是那些在几乎所有文档中都出现、但对搜索结果没有区分价值的高频词。因此,在全文搜索中可以忽略这些词。例如,几乎每篇英文文档中都有 a
和 the
这样的词,所以没有必要将它们写入索引。
不过,停用词会影响 tsvector
中的位置编号,从而间接影响文档的排名计算:
SELECT to_tsvector('english', 'in the list of stop words');
to_tsvector
----------------------------
'list':3 'stop':5 'word':6
上例中,位置 1、2 和 4 消失了,就是因为这些位置的词是停用 词。包含和不包含停用词的文本,在排名计算上的差异明显:
SELECT ts_rank_cd (to_tsvector('english', 'in the list of stop words'), to_tsquery('list & stop'));
ts_rank_cd
------------
0.05
SELECT ts_rank_cd (to_tsvector('english', 'list stop words'), to_tsquery('list & stop'));
ts_rank_cd
------------
0.1
是否将某个词视为停用词,由具体的字典决定。例如,ispell
字典会先将词进行标准化,再检查是否为停用词;而 Snowball
词干提取器会先查停用词列表。两者行为不同,是为了尽可能减少干扰项。
simple 字典
simple
模板的工作方式是将输入词元转换为小写,并在停用词文件中查找。如果在文件中找到该词,则返回空数组,表示该词被丢弃;如果没找到,就返回小写形式,作为标准化后的词素。
也可以配置字典,在不是停用词时返回 NULL
,让这个词元传递给下一个字典处理。
以下是一个使用 simple
模板定义字典的示例:
CREATE TEXT SEARCH DICTIONARY public.simple_dict (
TEMPLATE = pg_catalog.simple,
STOPWORDS = english
);
这里的 english
是一个停用词文件的基础名,完整文件名是 $SHAREDIR/tsearch_data/english.stop
,其中 $SHAREDIR
是 Apache Cloudberry 的共享数据目录,通常为 /usr/local/cloudberry-db-<version>/share/postgresql
。如果不确定路径,可以执行 pg_config --sharedir
查看。
该文件的格式是一行一个词,忽略空行和行尾空格,自动将大写转换为小写,但 不会做其他处理。
测试该字典的行为:
SELECT ts_lexize('public.simple_dict', 'YeS');
ts_lexize
-----------
{yes}
SELECT ts_lexize('public.simple_dict', 'The');
ts_lexize
-----------
{}
你也可以选择在词不在停用词文件中时返回 NULL
,而不是返回小写形式。只需将字典的 Accept
参数设为 false
:
ALTER TEXT SEARCH DICTIONARY public.simple_dict ( Accept = false );
SELECT ts_lexize('public.simple_dict', 'YeS');
ts_lexize
-----------
{yes}
SELECT ts_lexize('public.simple_dict', 'The');
ts_lexize
-----------
{}
当使用默认的 Accept = true
设置时,这种字典只适合放在字典链的最后一个位置,因为它不会将任何词元传递给后续字典。而当 Accept = false
时,该字典必须位于前面,并且后面必须至少有一个字典。
大多数类型的字典都依赖配置文件,比如停用词文件。这些文件 必须 使用 UTF-8 编码保存。如果数据库编码不同,系统会在读取时自动进行转换。
一般来说,一个数据库会话只会在首次使用某个字典时读取一次字典文件。如果修改了字典文件,而希望当前会话立刻加载更新内容,可以执行一次 ALTER TEXT SEARCH DICTIONARY
命令。这条命令可以不实际修改任何参数,仅用于强制刷新配置。
同义词字典(Synonym dictionary)
该字典模板用于创建可以将一个词替换为其同义词的字典。不支持短语匹配——如果需要处理短语,应使用同义词库模板(参见 Thesaurus Dictionary)。
同义词字典可以用于解决语言处理中的一些问题。例如,为了避免英文词干提取器将 "Paris" 错误地还原为 "pari",只需在同义词文件中添加一行 Paris paris
,并将该字典放在 english_stem
字典之前。例如:
SELECT * FROM ts_debug('english', 'Paris');
alias | description | token | dictionaries | dictionary | lexemes
-----------+-----------------+-------+----------------+--------------+---------
asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari}
CREATE TEXT SEARCH DICTIONARY my_synonym (
TEMPLATE = synonym,
SYNONYMS = my_synonyms
);
ALTER TEXT SEARCH CONFIGURATION english
ALTER MAPPING FOR asciiword
WITH my_synonym, english_stem;
SELECT * FROM ts_debug('english', 'Paris');
alias | description | token | dictionaries | dictionary | lexemes
-----------+-----------------+-------+---------------------------+------------+---------
asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris}
synonym
模板唯一必需的参数是 SYNONYMS
,它指定配置文件的基础名,上例中为 my_synonyms
。配置文件的完整路径将是 $SHAREDIR/tsearch_data/my_synonyms.syn
(其中 $SHAREDIR
是 Apache Cloudberry 安装目录中的共享数据目录)。文件格式是一行一个词项,词项和其同义词之间用空格分隔。文件中的空行和尾部空格会被忽略。
该模板还支持一个可选参数 CaseSensitive
,默认值为 false
。当设置为 false
时,词项和输入词元都将转换为小写后再比较;当设置为 true
时,二者将按原样大小写敏感地进行比较。
你也可以在配置文件中某个同义词的末尾添加星号(*
),表示该同义词是一个前缀。此星号在 to_tsvector()
中会被忽略,但在 to_tsquery()
中则会作为前缀匹配标记处理(参见 解析查询)。假设你在 $SHAREDIR/tsearch_data/synonym_sample.syn
文件中添加了以下内容:
postgres pgsql postgresql pgsql postgre pgsql
gogle googl
indices index*
那么查询会得到如下结果:
mydb=# CREATE TEXT SEARCH DICTIONARY syn (template=synonym, synonyms='synonym_sample');
mydb=# SELECT ts_lexize('syn', 'indices');
ts_lexize
-----------
{index}
(1 row)
mydb=# CREATE TEXT SEARCH CONFIGURATION tst (copy=simple);
mydb=# ALTER TEXT SEARCH CONFIGURATION tst ALTER MAPPING FOR asciiword WITH syn;
mydb=# SELECT to_tsvector('tst', 'indices');
to_tsvector
-------------
'index':1
(1 row)
mydb=# SELECT to_tsquery('tst', 'indices');
to_tsquery
------------
'index':*
(1 row)
mydb=# SELECT 'indexes are very useful'::tsvector;
tsvector
---------------------------------
'are' 'indexes' 'useful' 'very'
(1 row)
mydb=# SELECT 'indexes are very useful'::tsvector @@ to_tsquery('tst', 'indices');
?column?
----------
t
(1 row)