受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

12.6. 词典 #

词典用于消除不应参与搜索的词(stop words),并用于对词进行正规化,以便同一个词的不同派生形式能够匹配。成功完成正规化的词被称为词位。除了改善搜索质量,正规化和移除停用词还会减小文档的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模块所做的那样。

12.6.1. 停用词 #

停用词是非常常见、几乎出现在每个文档中的词,没有区分价值。因此,在全文搜索中可以忽略它们。例如,每篇英文文本都包含像 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 词典模板的工作方式是先把输入词元转换为小写,再根据停用词文件检查它。如果在文件中找到该词元,就返回一个空数组,从而丢弃该词元;否则,返回其小写形式作为正规化后的词位。或者,也可以把该词典配置为把非停用词报告为未识别,从而允许它们传递给列表中的下一个词典。

下面是一个使用 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

大多数词典类型都依赖配置文件,例如停用词文件。这些文件必须采用 UTF-8 编码保存。当它们被读入服务器时,如果数据库编码不同,就会被转换为实际的数据库编码。

Caution

通常,词典配置文件在某个数据库会话中第一次被使用时,只会被读取一次。如果你修改了配置文件,并希望强制现有会话加载新内容,可以对该词典执行一条 ALTER TEXT SEARCH DICTIONARY 命令。这可以是一条更新,也就是并不实际修改任何参数值的更新。

12.6.3. 同义词词典 #

这个词典模板用于创建把一个单词替换为同义词的词典。不支持短语(对此请使用分类词典模板 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。当CaseSensitivefalse时,同义词文件中的词会像输入词元一样折叠为小写。当它为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. 分类词典 #

一个分类词典(有时被简写成TZ)是一个词的集合,其中包括了词与短语之间的联系,即广义词(BT)、狭义词(NT)、首选词、非首选词、相关词等。

基本上一个分类词典会用一个首选词替换所有非首选词,并且也可选择地保留原始术语用于索引。PostgreSQL的分类词典的当前实现是同义词词典的一个扩展,并增加了短语支持。一个分类词典要求一个下列格式的配置文件:

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

其中冒号(:)符号扮演了一个短语及其替换之间的定界符。

分类词典会使用一个子词典(在词典配置中指定)在检查短语匹配之前正规化输入文本。只能选择一个子词典。如果子词典无法识别某个词,就会报错。在这种情况下,你应当避免使用该词,或者让子词典学会它。你可以在某个被索引词的开头放置一个星号(*),以跳过对子词典的应用,但所有样例词都必须能被子词典识别。

如果有多个短语匹配输入,则分类词典选择最长的那一个,并且使用最后的定义打破连结。

由子词典识别的特定停用词不能够被指定;改用?标记任何可以出现停用词的地方。例如,假定根据子词典athe是停用词:

? one ? two : swsw

匹配a one the twothe one a two;两者都将被swsw替换。

由于分类词典具备识别短语的能力,因此它必须记住自身状态并与解析器交互。分类词典会利用这些映射关系来判断自己是应当处理下一个词,还是停止累积。分类词典必须经过仔细配置。例如,如果分类词典只被映射到 asciiword 词元,那么像 one 7 这样的分类词典定义就无法工作,因为词元类型 uint 并没有映射给该分类词典。

Caution

分类词典会在建立索引时使用,因此其参数发生任何变化都要求重新建立索引。对于大多数其他词典类型,像增加或移除停用词这样的细小改动则不会强制重新建立索引。

12.6.4.1. 分类词典配置 #

要定义一个新的分类词典,可使用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;

12.6.4.2. 分类词典示例 #

考虑简单的天文词库 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 能匹配 thesaurus_astro 中的 supernovae stars,因为我们在分类词典定义中指定了 english_stem 词干分析器。它去掉了词尾的 es

如果既要为替换词建立索引,也要为原始短语建立索引,只需把原始短语也写到定义右侧即可:

supernovae stars : sn supernovae stars

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

12.6.5. Ispell 词典 #

Ispell词典模板支持形态词典,它可以把一个词的许多不同语言学形式正规化为同一个词位。例如,一个英语 Ispell 词典可以把搜索词 bank 的词尾变化和词形变化对应起来,例如 bankingbankedbanksbanks'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);
    

这里,DictFileAffFileStopWords指定词典、词缀和停用词文件的基础名称。停用词文件的格式和前面解释的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

这里的条件具有和正则表达式相似的格式。它可以使用分组[...][^...]。例如,[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

Note

MySpell 不支持复合词。Hunspell则对复合词有更好的支持。当前,PostgreSQL只实现了 Hunspell 中基本的复合词操作。

12.6.6. Snowball 词典 #

Snowball词典模板基于 Martin Porter 的一个项目,他是流行的英语 Porter 词干分析算法的发明者。Snowball 现在对许多语言提供词干分析算法(详见Snowball 站点)。每一个算法懂得按照其语言中的拼写,如何缩减词的常见变体形式为一个基础或词干。一个 Snowball 词典要求一个language参数来标识要用哪种词干分析器,并且可以选择地指定一个stopword文件名来给出一个要被消除的词列表(PostgreSQL的标准停用词列表也是由 Snowball 项目提供的)。例如,有一个内置的定义等效于

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

停用词文件格式和已经解释的一样。

一个Snowball词典识别所有的东西,不管它能不能简化该词,因此它应当被放置在词典列表的最后。把它放在任何其他词典前面是没有用处的,因为一个词元永远不会穿过它而进入到下一个词典。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。