全文搜索(或称为文本搜索)可以识别满足查询的自然语言文档,并可以选择按照与查询的相关性对文档排序。最常见的搜索类型是查找包含给定查询词的所有文档,并按文档与查询的相似度对文档排序。查询
和相似度
的概念非常灵活,具体取决于具体应用。最简单的搜索将查询
视为一组单词,将相似度
视为文档中查询词的频率。
文本搜索运算符已在数据库中存在多年。PostgreSQL具有针对文本数据类型的~
、~*
、LIKE
和ILIKE
运算符,但它们缺少现代信息系统所需的许多基本特性
即使是对于英语也是如此,也不存在语言支持。正则表达式不够用,因为它们不能简单地处理派生词,例如,satisfies
和satisfy
。您可能会错过包含satisfies
的文档,而当您搜索satisfy
时,您可能希望找到该文档。可以使用OR
搜索多个派生形式,但这很繁琐,而且容易出错(某些单词可能有数千个派生词)。
它们不提供任何搜索结果的排序(排名),当找到数千个匹配的文档时,这使得它们无效。
由于没有索引支持,它们往往很慢,因此它们必须为每次搜索处理所有文档。
全文索引允许预处理文档,并保存索引以供以后快速搜索。预处理包括
将文档解析为令牌。识别不同类别的令牌,例如数字、单词、复合词、电子邮件地址,以使它们可以被不同地处理是有用的。原则上,令牌类别依特定应用而定,但对于大多数目的来说,使用一组预先定义的类别就足够了。PostgreSQL使用解析器来执行此步骤。提供了标准解析器,并且可以针对特定需求创建自定义解析器。
将标记转换为词素。词素是一种字符串,与标记类似,但它已经规范化,这样才能使同一单词的不同形式变得相似。例如,规范化通常包括将大写字母转为小写字母,而且通常涉及移除后缀(例如英语中的s
或es
)。这允许搜索功能查找同一单词的不同变体形式,而无需繁琐地输入所有可能变体。此外,此步骤通常会消除停用词,这些单词非常常见,对于搜索而言毫无用处。(简而言之,标记是文档文本的原始片段,而词素则是被认为对于索引和搜索有用的单词。)PostgreSQL使用词典来执行此步骤。可以提供各种标准词典,并且可以根据具体需要创建自定义词典。
存储已预处理,针对搜索进行了优化的文档。例如,可以将每个文档表示为规范化词素的有序数组。除了词素,通常还需要存储位置信息供邻近度排名使用,这样包含查询单词更““密集”区域的文档会比散布式查询单词的文档获得更高的排名。
词典允许精确控制标记的规范化方式。使用适当的词典,你可以:
定义不应编入索引的停用词。
使用Ispell将同义词映射到一个单词。
使用同义词库将短语映射到一个单词。
使用Ispell词典将不同单词变体映射到规范形式。
使用Snowball词干分析规则将不同单词变体映射到规范形式。
提供数据类型tsvector
用于存储已预处理的文档,提供数据类型tsquery
用于表示已处理的查询(第 8.11 节)。这些数据类型提供了多种函数和操作符(第 9.13 节),其中最重要的是匹配操作符@@
,我们将在第 12.1.2 节中介绍它。可以使用索引加快全部文本搜索(第 12.9 节)。
在全文搜索系统中,文档是搜索的单元;例如,杂志文章或电子邮件消息。文本搜索引擎必须能够解析文档并存储词素(关键词)与其父文档的关联。稍后,这些关联用于搜索包含查询词的文档。
在 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
称为文档,但当然它只是完整文档的一个紧凑表示形式。
在 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_tsquery
、plainto_tsquery
和 phraseto_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) @@ 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
位于 x
之后,则它将匹配 y
;文档中 x
的其他出现不会阻止匹配。另一个示例是,x & y
通常仅要求 x
和 y
同时出现在文档中的某处,但(x & y) <-> z
要求 x
和 y
在同一位置匹配,出现在 z
之前。因此,此查询的行为与 x <-> z & y <-> z
不同,后者将匹配包含两个单独序列 x z
和 y z
的文档。(此特定查询按书面形式是无用功,因为 x
和 y
无法在同一位置匹配;但对于前缀匹配模式等更加复杂的情况,此类形式的查询可能是非常有用的。)
以上都是简单的文本搜索示例。如前所述,全文搜索功能包含执行更多操作的能力:跳过某些词的索引(阻碍词),处理同义词,并使用复杂的解析,例如,解析基于不仅仅是空格。此功能受文本搜索配置控制。PostgreSQL附带许多语言的预定义配置,您可以轻松创建自己的配置。(psql's \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/
区域中有一些附加解析器和模板的示例。)由于词典和配置只是对一些底层解析器和模板进行参数化和连接,因此不需要特殊权限来创建新词典或配置。本篇章后面将介绍创建自定义词典和配置的示例。