2024年9月26日:PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:开发版
不受支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3

12.1. 简介 #

全文搜索(或简称文本搜索)提供了识别满足查询的自然语言文档的功能,并可以选择根据与查询的相关性对它们进行排序。最常见的搜索类型是查找包含给定查询词的所有文档,并按其与查询的相似性返回。 查询相似性的概念非常灵活,并且取决于具体的应用程序。最简单的搜索将查询视为一组单词,并将相似性视为查询词在文档中的频率。

文本搜索操作符在数据库中已经存在多年了。PostgreSQL 具有用于文本数据类型的~~*LIKEILIKE操作符,但它们缺乏现代信息系统所需的许多基本属性。

  • 即使对于英语,也没有语言支持。正则表达式是不够的,因为它们无法轻松处理派生词,例如satisfiessatisfy。您可能会错过包含satisfies的文档,尽管您可能希望在搜索satisfy时找到它们。可以使用OR来搜索多个派生形式,但这既乏味又容易出错(有些单词可能有数千个派生词)。

  • 它们不提供搜索结果的排序(排名),这使得在找到数千个匹配文档时效率低下。

  • 它们往往很慢,因为没有索引支持,所以必须处理每次搜索的所有文档。

全文索引允许对文档进行预处理,并保存索引以供以后快速搜索。预处理包括

  • 将文档解析成标记。识别各种标记类别(例如,数字、单词、复杂单词、电子邮件地址)以便以不同的方式处理它们非常有用。原则上,标记类别取决于具体的应用程序,但对于大多数目的而言,使用预定义的类别集就足够了。PostgreSQL 使用解析器来执行此步骤。提供了一个标准解析器,并且可以为特定需求创建自定义解析器。

  • 将标记转换为词素。词素是一个字符串,就像标记一样,但它已被规范化,以便同一单词的不同形式变得相同。例如,规范化几乎总是包括将大写字母折叠为小写,并且通常涉及删除后缀(例如英语中的ses)。这允许搜索找到同一单词的不同形式,而无需费力地输入所有可能的变体。此外,此步骤通常会消除停用词,这些词是如此常见以至于对搜索没有用处。(简而言之,标记是文档文本的原始片段,而词素则是被认为对索引和搜索有用的单词。)PostgreSQL 使用词典来执行此步骤。提供了各种标准词典,并且可以为特定需求创建自定义词典。

  • 存储针对搜索优化的预处理文档。例如,每个文档可以表示为规范化词素的排序数组。除了词素之外,通常还需要存储位置信息以用于邻近性排序,以便包含更密集的查询词区域的文档比具有分散的查询词的文档获得更高的排名。

词典允许对标记如何规范化进行细粒度控制。使用适当的词典,您可以

  • 定义不应编入索引的停用词。

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

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

  • 使用Ispell词典将单词的不同变体映射到规范形式。

  • 使用Snowball词干提取规则将单词的不同变体映射到规范形式。

提供了一种数据类型tsvector用于存储预处理文档,以及一种类型tsquery用于表示处理后的查询(第8.11节)。这些数据类型有许多可用函数和操作符(第9.13节),其中最重要的一个是匹配操作符@@,我们在第12.1.2节中介绍了它。可以使用索引加速全文搜索(第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 m.mid = d.did AND m.mid = 12;

注意

实际上,在这些示例查询中,应使用coalesce来防止单个NULL属性导致整个文档的结果为NULL

另一种可能性是将文档存储为文件系统中的简单文本文件。在这种情况下,数据库可用于存储全文索引并执行搜索,并且可以使用一些唯一标识符从文件系统检索文档。但是,从数据库外部检索文件需要超级用户权限或特殊函数支持,因此这通常不如将所有数据保存在PostgreSQL内部方便。此外,将所有内容保存在数据库内部允许轻松访问文档元数据以辅助索引和显示。

出于文本搜索目的,每个文档都必须简化为预处理的tsvector格式。搜索和排名完全在文档的tsvector表示形式上执行——只有在选择文档以显示给用户时才需要检索原始文本。因此,我们通常将tsvector称为文档,但当然它只是完整文档的紧凑表示。

12.1.2. 基本文本匹配 #

PostgreSQL中的全文搜索基于匹配操作符@@,如果tsvector(文档)与tsquery(查询)匹配,则返回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 和 FOLLOWED BY 操作符组合多个词。(有关语法详细信息,请参见第8.11.2节。)有一些函数to_tsqueryplainto_tsqueryphraseto_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的元素是词素,假定它们已经规范化,因此ratsrat不匹配。

@@操作符也支持text输入,允许在简单情况下跳过将文本字符串显式转换为tsvectortsquery。可用的变体是

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

我们已经看到了前两个。表单text @@ tsquery等效于to_tsvector(x) @@ y。表单text @@ text等效于to_tsvector(x) @@ plainto_tsquery(y)

tsquery中,&(AND)操作符指定其两个参数都必须出现在文档中才能匹配。类似地,|(OR)操作符指定其参数中至少有一个必须出现,而!(NOT)操作符指定其参数必须出现才能匹配。例如,查询fat & ! rat匹配包含fat但不包含rat的文档。

可以使用<->(FOLLOWED BY)tsquery操作符搜索短语,该操作符仅当其参数的匹配项相邻且按给定顺序时才匹配。例如

SELECT to_tsvector('fatal error') @@ to_tsquery('fatal <-> error');
 ?column?
----------
 t

SELECT to_tsvector('error is not fatal') @@ to_tsquery('fatal <-> error');
 ?column?
----------
 f

FOLLOWED BY 运算符有一个更通用的版本,其形式为 <N>,其中 N 是一个整数,表示匹配词元位置之间的差值。 <1> 等同于 <->,而 <2> 允许在匹配之间恰好出现一个其他词元,以此类推。 phraseto_tsquery 函数利用此运算符构建一个 tsquery,当某些单词是停用词时,它可以匹配多词短语。例如

SELECT phraseto_tsquery('cats ate rats');
       phraseto_tsquery
-------------------------------
 'cat' <-> 'ate' <-> 'rat'

SELECT phraseto_tsquery('the cats ate the rats');
       phraseto_tsquery
-------------------------------
 'cat' <-> 'ate' <2> 'rat'

一个有时很有用的特殊情况是,<0> 可以用来要求两个模式匹配同一个单词。

可以使用括号来控制 tsquery 运算符的嵌套。在没有括号的情况下,| 的结合优先级最低,然后是 &,然后是 <->! 的结合优先级最高。

值得注意的是,当 AND/OR/NOT 运算符位于 FOLLOWED BY 运算符的参数内部时,它们的含义与不在内部时略有不同,因为在 FOLLOWED BY 内部,匹配的确切位置非常重要。例如,通常 !x 仅匹配不包含 x 的任何文档。但 !x <-> y 匹配 y,如果它不是紧跟在 x 之后;文档中其他地方出现的 x 不会阻止匹配。另一个例子是,x & y 通常只需要 xy 都出现在文档中的某个位置,但 (x & y) <-> z 要求 xy 在同一个位置匹配,紧接在 z 之前。因此,此查询的行为与 x <-> z & y <-> z 不同,后者将匹配包含两个独立序列 x zy z 的文档。(如上所写,此特定查询毫无用处,因为 xy 不能在同一位置匹配;但对于更复杂的情况,例如前缀匹配模式,此形式的查询可能很有用。)

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/ 区域中有一些附加解析器和模板的示例。)由于词典和配置只是参数化并连接一些底层解析器和模板,因此不需要任何特殊权限来创建新的词典或配置。本章后面将提供创建自定义词典和配置的示例。

提交更正

如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表单 报告文档问题。