Postgresql 中文操作指南

12.6. Dictionaries #

字典用于消除不应该在搜索中考虑的单词(stop words),并将单词 normalize,以便相同单词的不同派生形式匹配。一个成功标准化的单词称为 lexeme。除了提高搜索质量外,标准化和停用词的删除还减小了文档的 tsvector 表示的大小,从而提高了性能。标准化并不总是有语言含义,通常取决于应用程序语义。

一些标准化示例:

字典是一项程序,可以接收一个令牌作为输入并返回:

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 模块所做的那样。

12.6.1. Stop Words #

停用词是极其常见、几乎出现在每份文档中且没有歧义值的单词。因此,在全文检索的情况下,可以忽略它们。例如,每篇英文文本都包含 athe 等单词,因此将它们存储在索引中毫无用处。然而,停用词确实会影响 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 词干分析器首先检查停用词列表。行为不同的原因在于减少噪音的尝试。

12.6.2. Simple Dictionary #

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 仅在有至少一个后续字典时才有用。

Caution

大多数类型的字典都依赖于配置文件,例如停用词文件。这些文件 must 可以存储为 UTF-8 编码。当它们被读入服务器时,如果实际数据库编码不同,它们将被转换为实际数据库编码。

Caution

通常,当数据库会话首次在会话中使用字典时,它只读取一次字典配置文件。如果你修改了一个配置文件,并想要强制现有会话获取新内容,请针对该字典发出一个 ALTER TEXT SEARCH DICTIONARY 命令。这可以是一个不会实际更改任何参数值的“虚拟”更新。

12.6.3. Synonym Dictionary #

此词典模板用于创建将单词替换为同义词的词典。不支持短语(对此使用同义词表模板 ( Section 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() 中使用时,结果将是带有前缀匹配标记的查询项(参见 Section 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)

12.6.4. Thesaurus Dictionary #

同义库字典(有时缩写为 TZ)是一组包含有关单词和短语关系的信息的单词,即:较宽泛的术语 (BT)、较窄的术语 (NT)、首选术语、非首选术语、相关术语等。

基本上,同义库字典将所有非首选术语替换为一个首选术语,还可以选择保留原始术语用于索引。PostgreSQL 当前实现的同义库字典是对同义词字典的扩展,增加了 phrase 支持。同义库字典需要以下格式的配置文件:

# this is a comment
sample word(s) : indexed word(s)
more sample word(s) : more indexed word(s)
...

其中冒号 (:) 符号充当了一个短语与其替代内容之间的分隔符。

一个同义词词典使用一个 subdictionary(在词典的配置中指定)在检查短语匹配之前对输入文本进行规范化。一次只能选择一个子词典。如果子词典无法识别一个单词,则系统将报告一个错误。在这种情况下,你应该去除使用该单词或者教子词典如何识别它。你可以在一个已编过索引的单词的开头放置一个星号 (*) 来跳过将子词典应用到该单词,但所有示例单词 must 必须都是子词典已知的。

同义词词典如果有多个短语与输入相匹配,它将选择最长的匹配,并且使用最后一个定义来打破相等。

无法指定子词典识别的特定停用词;而应使用 ? 来标记任何停用词可以出现的位置。例如,假设 athe 根据子词典是停用词:

? one ? two : swsw

匹配 a one the twothe one a two;两者都应替换为 swsw

由于同义词词典具备识别短语的能力,所以它必须记住其状态并与解析器交互。同义词词典使用这些赋值来检查是否应处理下一个单词或者停止累积。必须仔细配置同义词词典。例如,如果同义词词典被指定仅处理 asciiword 令牌,则像 one 7 这样的同义词词典定义将不起作用,因为令牌类型 uint 未被指定给同义词词典。

Caution

在编制索引期间使用同义词库,所以同义词词典的参数中的任何更改都会 requires 重新编制索引。对于大多数其他类型的词典,诸如添加或去除停用词之类的更改并不会强制重新编制索引。

12.6.4.1. Thesaurus Configuration #

要定义一个新的同义词词典,请使用 thesaurus 模板。例如:

CREATE TEXT SEARCH DICTIONARY thesaurus_simple (
    TEMPLATE = thesaurus,
    DictFile = mythesaurus,
    Dictionary = pg_catalog.english_stem
);

在此处:

现在,可以在一个配置中将同义词词典 thesaurus_simple 绑定到所需的令牌类型,例如:

ALTER TEXT SEARCH CONFIGURATION russian
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH thesaurus_simple;

12.6.4.2. Thesaurus Example #

考虑一个简单的天文同义词词典 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_tsqueryto_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 匹配了 supernovae stars 中的 thesaurus_astro,因为我们在同义词定义中指定了 english_stem 词干提取器。词干提取器去除了 es

若要为原始短语和替换内容建立索引,只需将其包含在定义的右半部分:

supernovae stars : sn supernovae stars

SELECT plainto_tsquery('supernova star');
       plainto_tsquery
-----------------------------
 'sn' & 'supernova' & 'star'

12.6.5. Ispell Dictionary #

Ispell 词典模板支持 morphological dictionaries,它可以将一个单词的许多不同的语言形式标准化为相同的语素。例如,一个英语 Ispell 词典可以匹配搜索词 bank 的所有变格和变位形式,例如 bankingbankedbanksbanks'bank’s

标准 PostgreSQL 分发不包含任何 Ispell 配置文件。可以使用 Ispell 中找到多种语言的词典。此外,还支持一些更现代的词典文件格式 — MySpell (OO < 2.0.1) 和 Hunspell (OO >= 2.0.2)。可在 OpenOffice Wiki 中找到大量词典。

要创建 Ispell 词典,请执行这些步骤:

这里,DictFileAffFile、和 StopWords 指定了字典、词缀和停用词文件的基准名称。停用词文件的格式与上述 simple 词典类型中说明的相同。其他文件的格式此处未指定,但可以在上述网站中找到。

Ispell 词典通常只识别有限的单词,所以应该添加其他范围更大的词典;例如,可识别万物的 Snowball 词典。

Ispell 的 .affix 文件具有以下结构:

prefixes
flag *A:
    .           >   RE      # As in enter > reenter
suffixes
flag T:
    E           >   ST      # As in late > latest
    [^AEIOU]Y   >   -Y,IEST # As in dirty > dirtiest
    [AEIOU]Y    >   EST     # As in gray > grayest
    [^EY]       >   EST     # As in small > smallest

.dict 文件具有以下结构:

lapse/ADGRS
lard/DGRS
large/PRTY
lark/MRS

.dict 文件的格式是:

basic_form/affix_class_name

.affix 文件中,每个词缀标记都以以下格式描述:

condition > [-stripping_letters,] adding_affix

在此处,condition 的格式类似于正则表达式的格式。它可以使用分组 […​][^…​]。例如,[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]

一个词缀类的第一行是标题。词缀规则的字段在标题之后列出:

.dict 文件看起来像 Ispell 的 .dict 文件:

larder/M
lardy/RT
large/RSPMYT
largehearted

Note

MySpell 不支持复合词。Hunspell 对复合词提供复杂的支持。目前,PostgreSQL 只实现了 Hunspell 的基本复合词操作。

12.6.6. Snowball Dictionary #

Snowball 词典模板基于 Martin Porter 的一个项目,Martin Porter 是英语流行的 Porter 词干算法的发明者。Snowball 现在为多种语言提供词干算法(有关详细信息,请参阅 Snowball site)。每种算法都知道如何将单词的常用变体形式缩减为其语言中的基础或词干拼写。Snowball 词典需要 language 参数来标识要使用的词干提取器,还可以选择指定一个 stopword 文件名,该文件名给出了要消除的单词列表。(PostgreSQL 的标准停用词列表也由 Snowball 项目提供。)例如,有一个等效于以下内容的内置定义

CREATE TEXT SEARCH DICTIONARY english_stem (
    TEMPLATE = snowball,
    Language = english,
    StopWords = english
);

停用词文件格式与已说明的一样。

Snowball 词典识别所有内容,无论是否能够简化单词,所以它应放在词典列表的末尾。将其放在任何其他词典之前毫无用处,因为一个词条绝不会通过它传递到下一个词典。