12.1. 介绍

全文搜索(或只是文本搜索)提供满足查询的识别自然语言文档的能力, 并且任意地通过相关性查询进行排序。搜索最常见的类型是找到所有包含给定的查询术语的记录,并且以相似性的查 询顺序返回它们。 querysimilarity的概念是非常灵活的,取决于特定的应用。最简单的 搜索认为query是一组词,并且similarity为文档中的查询词出现的频率。

文本搜索操作符已经在数据库中存在多年。PostgreSQL为文本数据类型提供~, ~*, LIKEILIKE操作符,但它们缺乏许多通过现代信息系统要求的必要属性:

  • 没有语言的支持,即使是英语。正则表达式是不充分的,因为他们不能很容易地处理派生词, 比如, satisfiessatisfy。你可能会丢失包含satisfies的文档, 虽然你可能会发现他们在寻找satisfy。使用OR搜索多个派生形式是可能的,但这很繁琐, 而且容易出错(有些词可能会有上千的派生词)。

  • 他们没有提供搜索结果的分类(排序),当成千的匹配文档被发现时,这使得它们无效。

  • 他们往往比较缓慢,因为没有索引的支持,因此他们必须为每一个搜索处理所有文档。

全文索引允许文档被预处理,并且为后边的快速搜索保存一个索引。预处理包括:

  • 解析文档__标记。标识不同类别的记号是非常有用的, 例如,数字,词,复合词,电子邮件地址,这样他们可以用不同的方法来处理。原则上令牌类依赖于具体的应用, 但出于大多数的目的,可以使用一组预定义的类。PostgreSQL使用解析器来 执行这一步。提供了一种标准的解析器,以及为特定的需求创造的自定义分析器。

  • 转换标记为__词。词是一个字符串,就像一个标记,但它已经标准化, 这样同一个词的不同形式是一样的。例如,标准化几乎总是包括可折叠的大写字母到小写字母,往往涉及删除后缀(如英语中 的s 或者es )。这允许搜索找到同一个词的不同形式,没有繁琐的输入所有可能的变种。同时,这一步 通常删除屏蔽词,这是很常见的,他们对于搜索无用。(总之,标记是文档文本的原片段,而词汇被认 为是有用的索引和搜索的词。)PostgreSQL使用词典执行这一步。提供各种标准词典, 以及为特定的需求创造的自定义词典。

  • 为优化搜索存储预处理文档。比如,每个文档可以表示为标准化词汇排序数组。 伴随着词汇往往为邻近排序存储位置信息,这是理想的。因此包含查询词的"密集"区域的 文档比分散查询词分配到一个更高的顺序。

字典允许细粒度控制如何使用合适的字典规范化标记。你可以:

  • 定义不被索引的屏蔽词。

  • 使用Ispell映射同义词到一个词。

  • 使用同义词词典将短语映射到一个词。

  • 使用Ispell词典将词的不同形式映射到一种范式。

  • 使用Snowball词根规则将一个词的不同形式映射到一种范式。

一种数据类型tsvector用于存储预处理文档,以及类型tsquery 表示处理的查询(Section 8.11)。 为这些数据类型提供很多的函数和操作符(Section 9.13),其中最重要的是匹配运算符@@,将在Section 12.1.2中介绍。 全文搜索可以使用索引进行加速(Section 12.9)。

12.1.1. 文档是什么?

一个文档是全文搜索系统的搜索单元;例如,杂志上的一篇文章或电子邮件消息。 文本搜索引擎必须能够解析文档,而且可以存储它们父文档词(关键词)的联系性。之后, 这些联系用来搜索包含查询词的文档。

在PostgreSQL中搜索,文档通常是一个数据库表中一行的文本字段,或者这些字段的可能组合(级联), 可能存储在多个表中或者动态地获得。换句话说,一个文档可以由索引的不同部分构成,它不可能随时随地作 为一个整体存储。比如:

SELECT title || ' ' ||  author || ' ' ||  abstract || ' ' || body AS document
FROM messages
WHERE mid = 12;

SELECT m.title || ' ' || m.author || ' ' || m.abstract || ' ' || d.body AS document
FROM messages m, docs d
WHERE mid = did AND mid = 12;

Note: 注意:实际上,在这些示例查询中,coalesce使用时应防止一个独立的NULL属性导致整个文档的NULL结果。

另外一个可能性是在文档系统中作为简单的文本文档存储。在这种情况下,数据库可以用于存储全 文索引并且执行搜索,同时使用一些唯一标识从文件系统中检索文档。然而,从外部检索文件,数据库 需要拥有超级用户权限或者特殊函数支持,因此比把所有数据保存在PostgreSQL中相比较, 这往往不太方便。同时,保持所有的数据在数据库里面允许轻松访问文档的元数据以帮助索引和显示。

为了文本搜索目的,每个文档必须减少到预处理tsvector格式。在文档的tsvector表示形式上完整的执行搜索 和排序—当为了显示给用户来选择文档时,只需要检索原文本。因此我们常说的tsvector作为文档,当然它仅仅是 完整文档的一种紧凑表示。

12.1.2. 基本文本匹配

PostgreSQL中的全文搜索基于匹配算子@@,如果一个tsvector(document)匹配一个tsquery(query), 则返回true。不管哪个数据类型先被重写:

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
 ?column?
----------
 t

SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
 ?column?
----------
 f

正如上面例子表明,一个tsquery不仅仅是原文本,更多的是一个tsvector。一个包含搜索条件的tsquery, 必须是已经标准化的词,并且可能使用AND, OR, 和 NOT操作符连接多个术语(详情请见Section 8.11)。 函数to_tsqueryplainto_tsquery在将用户书写文本转换成一个合适的tsquery是非常有帮助的。 比如通过标准化文本中的词。类似的,to_tsvector用于解析和标准化文档字符串。因此在实践中文本搜索匹配 可能看起来更像这样:

SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
 ?column? 
----------
 t

观察这个匹配可能不会成功,如果写成这样:

SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat');
 ?column? 
----------
 f

由于这儿没有发生词rats的标准化。一个tsvector的元素是词,假设已经被标准化,所以rats不匹配rat

@@操作符也支持text输入,允许一个文本字符串的显示转换为tsvector或者在简单情况下忽略tsquery。 可用形式是:

tsvector @@ tsquery
tsquery  @@ tsvector
text @@ tsquery
text @@ text

我们已经看到了前面两种,形式 text @@ tsquery等价于to_tsvector(x) @@ ytext @@ text等价于 to_tsvector(x) @@ plainto_tsquery(y)

12.1.3. 配置

上面是所有简单文本搜索例子。如前所述,全文搜索功能还有能力做更多事情:忽略索引某个词(屏蔽词), 过程同义词和使用复杂解析,比如:不仅仅基于空白格的解析。这些功能通过文本搜索配置控制。 PostgreSQL来自多语言的预先定义的配置,并且你也可以很容易的创建你自己的配置(psql的\dF 命令显示了 所有可用配置)。

在安装期间选择一个合适的配置,并且在postgresql.conf中相应的设置default_text_search_config。 如果为了整个集群使用同一个文本搜索配置你可以使用postgresql.conf中的值。为了在集群中使用不同配置,但是在任何其他一个数据库的同一 配置中使用ALTER DATABASE ... SET。否则,你可以在每个会话中设置default_text_search_config

每个依赖于配置的文本搜索函数有一个可选的regconfig参数,因此可以明确声明使用的配置。 仅当忽略这些参数的时候,才使用default_text_search_config

为了更方便的建立自定义文本搜索配置,从简单的数据库对象中建立了配置。 PostgreSQL文本搜索功能提供了四种类型的配置相关的数据库对象:

  • 文本搜索解析器打破文档标记,并且分类每个标记(比如,词和数字)。

  • 文本搜索词典把标记转换成规范格式并且拒绝屏蔽词。

  • 文本搜索模板提供潜在的词典功能(一个词典简单的指定一个模板,并且为模板设置参数)。

  • 文本搜索配置选择一个解析器,并且使用一系列词典规范化语法分析器产生的标记。

文本搜索解析器和模板是从低层次的C函数建立的;因此它需要C程序能力开发新产品, 并且需要超级用户权限安装到数据库中(在PostgreSQL发布的contrib/范围内有附加解析器和模板的实例)。 因为词典和配置仅仅参数化并且连接到一些潜在的解析器和模板上,创建一个新的词典或者配置不需要特定的权限。 创建自定义词典和配置实例出现在本章节的后面。