pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
词典用于消除不应参与搜索的词(停用词),并用于对词进行正规化,以便同一个词的不同派生形式能够匹配。成功完成正规化的词被称为词位。除了改善搜索质量,正规化和移除停用词还会减小文档的 tsvector 表示,从而提高性能。正规化并不总是具有语言学意义,而且通常依赖于应用的语义。
一些正规化的示例:
语言学上的例子 — Ispell 词典尝试把输入词约简为一种正规化形式;词干分析器词典则去掉词尾
URL 地址可以规范化,以便让等价的 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
PostgreSQL 为许多语言提供了预定义的词典。也有多种预定义模板可以被用于创建带自定义参数的新词典。每一种预定义词典模板在下面描述。如果没有合适的现有模板,可以创建新的;示例见 PostgreSQL 发行版的 contrib/目录。
文本检索配置把一个解析器与一组用于处理解析器输出词元的词典绑定在一起。对于解析器可能返回的每一种词元类型,配置都会指定一个单独的词典列表。当解析器找到该类型的词元时,会按顺序依次查询列表中的每个词典,直到有某个词典把它识别为已知词。如果它被识别为停用词,或者没有任何词典识别它,那么该词元就会被丢弃,既不会建立索引,也不会参与搜索。通常,第一个返回非 NULL 输出的词典就决定结果,后续词典不会再被查询;但过滤词典可以把给定单词替换为一个修改后的单词,再传递给后续词典。
配置词典列表的一般规则是,把适用范围最窄、最专门的词典放在最前面,然后是更通用的词典,最后以一个非常通用的词典收尾,例如 Snowball 词干分析器,或能识别所有内容的 simple。例如,对于一个天文学相关搜索(配置名为 astro_en),可以把词元类型 asciiword(ASCII 词)绑定到一个天文学术语同义词词典、一个通用英语词典,以及一个 Snowball 英语词干分析器:
ALTER TEXT SEARCH CONFIGURATION astro_en
ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;
过滤词典可以放在列表中的任何位置,但放在末尾没有用处。过滤词典可用于先对词做部分正规化,以简化后续词典的工作。例如,可以用过滤词典去掉带重音字母中的重音符号,就像 unaccent 模块所做的那样。
停用词是非常常见、几乎出现在每个文档中的词,没有区分价值。因此,在全文检索中可以忽略它们。例如,每篇英文文本都包含像 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 模板的词典定义示例:
CREATE TEXT SEARCH DICTIONARY public.simple_dict (
TEMPLATE = pg_catalog.simple,
STOPWORDS = english
);
其中,english 是停用词文件的基名。文件完整名称将是 $SHAREDIR/tsearch_data/english.stop,其中 $SHAREDIR 表示 PostgreSQL 安装的共享数据目录,通常是 /usr/local/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
-----------
SELECT ts_lexize('public.simple_dict', 'The');
ts_lexize
-----------
{}
在默认设置 Accept = true 下,只有把 simple 词典放在词典列表末尾才有意义,因为它不会把任何词元传递给后续词典。相反,Accept = false 只有在后面至少还有一个词典时才有用。
大多数词典类型都依赖配置文件,例如停用词文件。这些文件必须采用 UTF-8 编码保存。当它们被读入服务器时,如果数据库编码不同,就会被转换为实际的数据库编码。
通常,词典配置文件在某个数据库会话中第一次被使用时,只会被读取一次。如果你修改了配置文件,并希望强制现有会话加载新内容,可以对该词典执行一条 ALTER TEXT SEARCH DICTIONARY 命令。这可以是一条“假”更新,也就是并不实际修改任何参数值的更新。
这个词典模板用于创建把一个单词替换为同义词的词典。不支持短语(对此请使用分类词典模板第 12.6.4 节)。同义词词典可以用来克服语言学上的问题,例如防止英语词干分析词典把“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 表示 PostgreSQL 安装的共享数据目录)。该文件格式是每行一个要被替换的词,后面跟着它的同义词,用空白分隔。空行和结尾的空格会被忽略。
synonym 模板还有一个可选参数 CaseSensitive,默认值为 false。当 CaseSensitive 为 false 时,同义词文件中的词会像输入词元一样折叠为小写。当它为 true 时,词和词元都不会被折叠为小写,而是按原样比较。
可以在配置文件中把星号(*)放在同义词末尾,表示该同义词是一个前缀。当该条目用于 to_tsvector() 时,星号会被忽略;但当它用于 to_tsquery() 时,结果将是一个带有前缀匹配标记的查询项(见第 12.3.2 节)。例如,假设在 $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)
一个分类词典(有时被简写成 TZ)是一个词的集合,其中包括了词与短语之间的联系,即广义词(BT)、狭义词(NT)、首选词、非首选词、相关词等。
分类词典基本上会用一个首选词替换所有非首选词,并且也可以选择保留原始词用于索引。PostgreSQL 的分类词典的当前实现是同义词词典的一个扩展,并增加了短语支持。一个分类词典需要一个采用以下格式的配置文件:
# 这是一条注释 示例词(一个或多个) : 索引词(一个或多个) 更多示例词(一个或多个) : 更多索引词(一个或多个) ...
其中冒号(:)符号用作短语及其替换词之间的分隔符。
分类词典会使用一个子词典(在词典配置中指定)在检查短语匹配之前正规化输入文本。只能选择一个子词典。如果子词典无法识别某个词,就会报错。在这种情况下,你应当避免使用该词,或者让子词典学会它。你可以在某个被索引词的开头放置一个星号(*),以跳过将子词典应用于该词,但所有样例词都必须能被子词典识别。
如果有多个短语匹配输入,分类词典会选择最长的匹配;长度相同时使用最后的定义。
由子词典识别的特定停用词不能够被指定;改用?标记可出现任意停用词的位置。例如,假定根据子词典 a 和 the 是停用词:
? one ? two : swsw
匹配 a one the two 和 the one a two;两者都将被 swsw 替换。
由于分类词典具备识别短语的能力,因此它必须记住自身状态并与解析器交互。分类词典会利用这些映射关系来判断自己是应当处理下一个词,还是停止累积。分类词典必须经过仔细配置。例如,如果分类词典只被映射到 asciiword 词元,那么像 one 7 这样的分类词典定义就无法工作,因为词元类型 uint 并没有映射给该分类词典。
分类词典会在建立索引时使用,因此其参数发生任何变化都要求重新建立索引。对于大多数其他词典类型,像增加或移除停用词这样的细小改动则不会强制重新建立索引。
要定义一个新的分类词典,可使用 thesaurus 模板。例如:
CREATE TEXT SEARCH DICTIONARY thesaurus_simple (
TEMPLATE = thesaurus,
DictFile = mythesaurus,
Dictionary = pg_catalog.english_stem
);
这里:
thesaurus_simple 是新词典的名称
mythesaurus 是分类词典配置文件的基名(它的全名将是 $SHAREDIR/tsearch_data/mythesaurus.ths,其中 $SHAREDIR 表示安装的共享数据目录)。
pg_catalog.english_stem 是要用于分类词典正规化的子词典(这里是一个 Snowball 英语词干分析器)。注意子词典将拥有它自己的配置(例如停用词),但这里没有展示。
现在可以在配置中把分类词典 thesaurus_simple 绑定到想要的词元类型上,例如:
ALTER TEXT SEARCH CONFIGURATION russian
ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
WITH thesaurus_simple;
考虑一个简单的天文分类词典 thesaurus_astro,其中包含一些天文单词组合:
supernovae stars : sn crab nebulae : crab
下面我们创建一个词典,并把一些词元类型绑定到天文分类词典和英语词干分析器上:
CREATE TEXT SEARCH DICTIONARY thesaurus_astro (
TEMPLATE = thesaurus,
DictFile = thesaurus_astro,
Dictionary = english_stem
);
ALTER TEXT SEARCH CONFIGURATION russian
ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
WITH thesaurus_astro, english_stem;
现在我们可以看看它是如何工作的。ts_lexize 对测试分类词典并不十分有用,因为它把输入当作一个单独的词元。相反,我们可以使用 plainto_tsquery 和 to_tsvector,它们会把输入字符串拆分成多个词元:
SELECT plainto_tsquery('supernova star');
plainto_tsquery
-----------------
'sn'
SELECT to_tsvector('supernova star');
to_tsvector
-------------
'sn':1
原则上,如果把参数用引号括起来,也可以使用 to_tsquery:
SELECT to_tsquery('''supernova star''');
to_tsquery
------------
'sn'
请注意,supernova star 能匹配 thesaurus_astro 中的 supernovae stars,因为我们在分类词典定义中指定了 english_stem 词干分析器。它去掉了词尾的 e 和 s。
如果既要为替换词建立索引,也要为原始短语建立索引,只需把原始短语也写到定义右侧即可:
supernovae stars : sn supernovae stars
SELECT plainto_tsquery('supernova star');
plainto_tsquery
-----------------------------
'sn' & 'supernova' & 'star'
Ispell 词典模板支持形态词典,它可以把一个词的许多不同语言学形式正规化为同一个词位。例如,一个英语 Ispell 词典可以匹配搜索词 bank 的所有变格和变位形式,例如 banking、banked、banks、banks' 和 bank's。
标准 PostgreSQL 发行版不包含任何 Ispell 配置文件。大量语言的词典可从 Ispell 获取。此外,还支持一些更现代的词典文件格式 — MySpell (OO < 2.0.1) 和 Hunspell(OO >= 2.0.2)。在 OpenOffice Wiki 上有大量词典可用。
要创建一个 Ispell 词典,请执行以下步骤:
下载词典配置文件。OpenOffice 扩展文件的扩展名是.oxt。需要提取.aff 和.dic 文件,将扩展名改为.affix 和.dict。对于某些词典文件,还需要使用下面的命令把字符转换成 UTF-8 编码(例如挪威语词典):
iconv -f ISO_8859-1 -t UTF-8 -o nn_no.affix nn_NO.aff iconv -f ISO_8859-1 -t UTF-8 -o nn_no.dict nn_NO.dic
复制文件到 $SHAREDIR/tsearch_data 目录
用下面的命令把文件载入到 PostgreSQL:
CREATE TEXT SEARCH DICTIONARY english_hunspell (
TEMPLATE = ispell,
DictFile = en_us,
AffFile = en_us,
Stopwords = english);
这里,DictFile、AffFile 和 StopWords 指定词典、词缀和停用词文件的基名。停用词文件的格式和前面解释的 simple 词典类型相同。其他文件的格式在这里没有指定,但是也可以从上面提到的网站获得。
Ispell 词典通常只能识别有限的词,因此其后应跟着另一个覆盖范围更广的词典;例如,一个 Snowball 词典,它可以识别所有输入。
Ispell 的.affix 文件具有下面的结构:
prefixes
flag *A:
. > RE # 例如 enter > reenter
suffixes
flag T:
E > ST # 例如 late > latest
[^AEIOU]Y > -Y,IEST # 例如 dirty > dirtiest
[AEIOU]Y > EST # 例如 gray > grayest
[^EY] > EST # 例如 small > smallest
.dict 文件具有下面的结构:
lapse/ADGRS lard/DGRS large/PRTY lark/MRS
.dict 文件的格式是:
基本词形/词缀类名
在.affix 文件中,每一个词缀标志以下面的格式描述:
条件 > [-删除的字母,] 添加的词缀
这里的条件具有和正则表达式相似的格式。它可以使用分组[...] 和[^...]。例如,[AEIOU]Y 表示词的最后一个字母是"y" 并且倒数第二个字母是"a"、"e"、"i"、"o" 或者"u"。[^EY] 表示最后一个字母既不是"e" 也不是"y"。
Ispell 词典支持划分复合词,这是一个有用的特性。注意词缀文件应该用 compoundwords controlled 语句指定一个特殊标志,用于标记词典中可以参与构成复合词的词:
compoundwords controlled z
下面是挪威语的一些示例:
SELECT ts_lexize('norwegian_ispell', 'overbuljongterningpakkmesterassistent');
{over,buljong,terning,pakk,mester,assistent}
SELECT ts_lexize('norwegian_ispell', 'sjokoladefabrikk');
{sjokoladefabrikk,sjokolade,fabrikk}
MySpell 格式是 Hunspell 格式的一个子集。Hunspell 的.affix 文件具有下面的结构:
PFX A Y 1 PFX A 0 re . SFX T N 4 SFX T 0 st e SFX T y iest [^aeiou]y SFX T 0 est [aeiou]y SFX T 0 est [^ey]
一个词缀类的第一行是首部。首部后面列出了词缀规则的字段:
参数名(PFX 或者 SFX)
标志(词缀类的名称)
从该词的开始(前缀)或者结尾(后缀)剥离字符
增加词缀
和正则表达式格式类似的条件。
.dict 文件看起来和 Ispell 的.dict 文件相似:
larder/M lardy/RT large/RSPMYT largehearted
MySpell 不支持复合词。Hunspell 则对复合词有更好的支持。当前,PostgreSQL 只实现了 Hunspell 中基本的复合词操作。
Snowball 词典模板基于 Martin Porter 的一个项目,他是流行的英语 Porter 词干分析算法的发明者。Snowball 现在对许多语言提供词干分析算法(详见 Snowball 站点)。每种算法都知道如何把其语言中词的常见变体约简为基本拼写形式,也就是词干。一个 Snowball 词典要求一个 language 参数来标识要用哪种词干分析器,并且可以选择指定一个 stopword 文件名来给出一个要被消除的词列表(PostgreSQL 的标准停用词列表也是由 Snowball 项目提供的)。例如,有一个内置的定义等效于
CREATE TEXT SEARCH DICTIONARY english_stem (
TEMPLATE = snowball,
Language = english,
StopWords = english
);
停用词文件格式和已经解释的一样。
一个 Snowball 词典识别所有输入,不管它能不能简化该词,因此它应当被放置在词典列表的最后。把它放在任何其他词典前面是没有用处的,因为一个词元永远不会穿过它而进入到下一个词典。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。