2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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 的元素是词素,假定它们已经规范化,因此 rats 不匹配 rat

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

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

前两个我们已经见过。 text @@ tsquery 形式等同于 to_tsvector(x) @@ ytext @@ 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,该 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_configpostgresql.conf 中的值适用于整个集群使用相同的文本搜索配置。要使集群内使用不同的配置,但每个数据库内使用相同的配置,请使用 ALTER DATABASE ... SET。否则,您可以在每个会话中设置 default_text_search_config

每个依赖于配置的文本搜索函数都有一个可选的 regconfig 参数,因此可以显式指定要使用的配置。default_text_search_config 仅在省略此参数时使用。

为了更容易地构建自定义文本搜索配置,配置是基于更简单的数据库对象构建的。 PostgreSQL 的文本搜索功能提供了四种类型的与配置相关的数据库对象:

  • 文本搜索解析器 将文档分解成词元,并对每个词元进行分类(例如,按单词或数字)。

  • 文本搜索词典 将词元转换为规范形式并拒绝停用词。

  • 文本搜索模板 提供词典的底层函数。(词典只需指定一个模板和该模板的一组参数。)

  • 文本搜索配置 选择一个解析器和一组词典,用于规范化解析器产生的词元。

文本搜索解析器和模板是基于低级 C 函数构建的;因此,开发新的解析器和模板需要 C 编程能力,并需要超级用户权限才能将其安装到数据库中。(在 PostgreSQL 发行版的 contrib/ 区域中有附加解析器和模板的示例。)由于词典和配置只是对底层解析器和模板进行参数化和连接,因此创建新的词典或配置不需要特殊权限。本章后面将给出创建自定义词典和配置的示例。

提交更正

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