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.3. 控制全文搜索 #

要实现全文搜索,必须有一个函数用于从文档创建 tsvector,以及一个用户查询创建 tsquery。另外,我们需要以有用的顺序返回结果,因此需要一个函数来比较文档与查询的相关性。能够方便地显示结果也很重要。 PostgreSQL 为所有这些功能提供了支持。

12.3.1. 解析文档 #

PostgreSQL 提供了 to_tsvector 函数,用于将文档转换为 tsvector 数据类型。

to_tsvector([ config regconfig, ] document text) returns tsvector

to_tsvector 将文本文档解析为标记 (tokens),将标记归约为词元 (lexemes),并返回一个 tsvector,其中列出了词元及其在文档中的位置。文档根据指定的或默认的全文搜索配置进行处理。这是一个简单的例子:

SELECT to_tsvector('english', 'a fat  cat sat on a mat - it ate a fat rats');
                  to_tsvector
-----------------------------------------------------
 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

在上面的例子中,我们看到生成的 tsvector 不包含单词 aonit,单词 rats 变成了 rat,并且标点符号 - 被忽略了。

to_tsvector 函数在内部调用一个解析器,该解析器将文档文本分解成标记,并为每个标记分配一个类型。对于每个标记,会查询一个字典列表(第 12.6 节),该列表可以根据标记的类型而变化。第一个 识别 该标记的字典会发出一个或多个表示该标记的规范化 词元。例如,rats 变成 rat 是因为其中一个字典识别出 ratsrat 的复数形式。有些单词被识别为 停用词第 12.6.1 节),由于它们出现的频率太高而对搜索无用,因此会被忽略。在我们的例子中,这些词是 aonit。如果没有字典能够识别该标记,那么它也会被忽略。在本例中,这种情况发生在标点符号 - 上,因为实际上没有为它的标记类型(Space symbols)分配字典,这意味着空格标记永远不会被索引。解析器、字典以及要索引的标记类型的选择由选定的全文搜索配置(第 12.7 节)决定。在同一个数据库中可以存在许多不同的配置,并且可以为各种语言提供预定义的配置。在我们的例子中,我们使用了英语的默认配置 english

可以使用 setweight 函数为 tsvector 的条目标记一个给定的 权重,权重可以是字母 ABCD 中的一个。这通常用于标记来自文档不同部分(如标题或正文)的条目。之后,这些信息可用于对搜索结果进行排名。

由于 to_tsvector(NULL) 会返回 NULL,因此建议在字段可能为 null 时使用 coalesce。以下是从结构化文档创建 tsvector 的推荐方法:

UPDATE tt SET ti =
    setweight(to_tsvector(coalesce(title,'')), 'A')    ||
    setweight(to_tsvector(coalesce(keyword,'')), 'B')  ||
    setweight(to_tsvector(coalesce(abstract,'')), 'C') ||
    setweight(to_tsvector(coalesce(body,'')), 'D');

在这里,我们使用了 setweight 来标记最终 tsvector 中每个词元的来源,然后使用 tsvector 连接运算符 || 合并标记的 tsvector 值。(第 12.4.1 节 详细介绍了这些操作。)

12.3.2. 解析查询 #

PostgreSQL 提供了 to_tsqueryplainto_tsqueryphraseto_tsquerywebsearch_to_tsquery 函数,用于将查询转换为 tsquery 数据类型。to_tsquery 提供了比 plainto_tsqueryphraseto_tsquery 更多的功能访问,但对输入的容错性较低。websearch_to_tsqueryto_tsquery 的简化版本,具有替代语法,类似于 Web 搜索引擎使用的语法。

to_tsquery([ config regconfig, ] querytext text) returns tsquery

to_tsqueryquerytext 创建一个 tsquery 值,querytext 必须由 tsquery 运算符 & (AND)、| (OR)、! (NOT) 和 <-> (FOLLOWED BY) 分隔的单个标记组成,并可能使用括号分组。换句话说,to_tsquery 的输入必须已经遵循 tsquery 输入的一般规则,如 第 8.11.2 节 所述。区别在于,虽然基本的 tsquery 输入按原样接受标记,但 to_tsquery 使用指定的或默认的配置将每个标记规范化为词元,并丢弃根据配置是停用词的任何标记。例如:

SELECT to_tsquery('english', 'The & Fat & Rats');
  to_tsquery
---------------
 'fat' & 'rat'

与基本 tsquery 输入一样,可以为每个词元附加权重,以将其限制为仅匹配具有这些权重的 tsvector 词元。例如:

SELECT to_tsquery('english', 'Fat | Rats:AB');
    to_tsquery
------------------
 'fat' | 'rat':AB

此外,还可以为词元附加 * 以指定前缀匹配:

SELECT to_tsquery('supern:*A & star:A*B');
        to_tsquery
--------------------------
 'supern':*A & 'star':*AB

这样的词元将匹配 tsvector 中以给定字符串开头的任何单词。

to_tsquery 还可以接受带引号的短语。这主要在配置包含可能根据这些短语触发的同义词典时有用。在下面的示例中,同义词典包含规则 supernovae stars : sn

SELECT to_tsquery('''supernovae stars'' & !crab');
  to_tsquery
---------------
 'sn' & !'crab'

不带引号的情况下,对于未被 AND、OR 或 FOLLOWED BY 运算符分隔的标记,to_tsquery 将生成语法错误。

plainto_tsquery([ config regconfig, ] querytext text) returns tsquery

plainto_tsquery 将未格式化的文本 querytext 转换为 tsquery 值。文本的解析和规范化方式与 to_tsvector 类似,然后将 & (AND) tsquery 运算符插入到保留的单词之间。

示例:

SELECT plainto_tsquery('english', 'The Fat Rats');
 plainto_tsquery
-----------------
 'fat' & 'rat'

请注意,plainto_tsquery 不会识别其输入中的 tsquery 运算符、权重标签或前缀匹配标签。

SELECT plainto_tsquery('english', 'The Fat & Rats:C');
   plainto_tsquery
---------------------
 'fat' & 'rat' & 'c'

在这里,所有输入的标点符号都被丢弃了。

phraseto_tsquery([ config regconfig, ] querytext text) returns tsquery

phraseto_tsquery 的行为与 plainto_tsquery 非常相似,不同之处在于它在保留的单词之间插入 <-> (FOLLOWED BY) 运算符而不是 & (AND) 运算符。此外,停用词不会被简单丢弃,而是通过插入 <N> 运算符而不是 <-> 运算符来处理。此函数在搜索精确的词元序列时很有用,因为 FOLLOWED BY 运算符会检查词元顺序,而不仅仅是所有词元是否存在。

示例:

SELECT phraseto_tsquery('english', 'The Fat Rats');
 phraseto_tsquery
------------------
 'fat' <-> 'rat'

plainto_tsquery 一样,phraseto_tsquery 函数不会识别其输入中的 tsquery 运算符、权重标签或前缀匹配标签。

SELECT phraseto_tsquery('english', 'The Fat & Rats:C');
      phraseto_tsquery
-----------------------------
 'fat' <-> 'rat' <-> 'c'
websearch_to_tsquery([ config regconfig, ] querytext text) returns tsquery

websearch_to_tsquery 使用替代语法从 querytext 创建 tsquery 值,在该语法中,简单的未格式化文本是有效的查询。与 plainto_tsqueryphraseto_tsquery 不同,它还识别某些运算符。此外,此函数永远不会引发语法错误,这使得可以使用原始的用户提供的输入进行搜索。支持以下语法:

  • 未加引号的文本:未放在引号内的文本将转换为用 & 运算符分隔的术语,就像由 plainto_tsquery 处理一样。

  • "加引号的文本":放在引号内的文本将转换为用 <-> 运算符分隔的术语,就像由 phraseto_tsquery 处理一样。

  • OR:单词 or 将转换为 | 运算符。

  • -:一个短划线将转换为 ! 运算符。

其他标点符号将被忽略。因此,与 plainto_tsqueryphraseto_tsquery 一样,websearch_to_tsquery 函数不会识别其输入中的 tsquery 运算符、权重标签或前缀匹配标签。

示例

SELECT websearch_to_tsquery('english', 'The fat rats');
 websearch_to_tsquery
----------------------
 'fat' & 'rat'
(1 row)

SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');
       websearch_to_tsquery
----------------------------------
 'supernova' <-> 'star' & !'crab'
(1 row)

SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');
       websearch_to_tsquery
-----------------------------------
 'sad' <-> 'cat' | 'fat' <-> 'rat'
(1 row)

SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"');
         websearch_to_tsquery
---------------------------------------
 'signal' & !( 'segment' <-> 'fault' )
(1 row)

SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->');
 websearch_to_tsquery
----------------------
 'dummi' & 'queri'
(1 row)

12.3.3. 对搜索结果进行排名 #

排名旨在衡量文档与特定查询的相关程度,以便在有许多匹配项时,优先显示最相关的项。PostgreSQL 提供了两个预定义的排名函数,它们考虑了词汇、邻近性和结构信息;也就是说,它们考虑查询词在文档中出现的频率,词在文档中互相靠近的程度,以及它们出现的文档部分的相对重要性。然而,相关性的概念是模糊且高度依赖于应用的。不同的应用可能需要额外的排名信息,例如文档修改时间。内置的排名函数只是示例。您可以编写自己的排名函数,和/或将它们的结果与其他因素结合起来,以满足您的特定需求。

目前可用的两个排名函数是:

ts_rank([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

根据匹配词元频率对向量进行排名。

ts_rank_cd([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

此函数计算给定文档向量和查询的 覆盖密度 排名,如 Clarke、Cormack 和 Tudhope 在期刊《Information Processing and Management》1999 年发表的论文“Relevance Ranking for One to Three Term Queries”中所述。覆盖密度与 ts_rank 排名类似,但考虑了匹配词元之间的邻近性。

此函数需要词元位置信息才能执行计算。因此,它会忽略 tsvector 中的任何 已剥离 词元。如果输入中没有未剥离的词元,则结果将为零。(有关 strip 函数和 tsvector 中位置信息的信息,请参阅 第 12.4.1 节。)

对于这两个函数,可选的 weights 参数提供了根据词元标记方式来加权词元实例的能力。权重数组按以下顺序指定了每个词元类别的加权程度:

{D-weight, C-weight, B-weight, A-weight}

如果未提供 weights,则使用这些默认值:

{0.1, 0.2, 0.4, 1.0}

通常,权重用于标记文档特殊区域(如标题或初始摘要)中的单词,以便它们比文档正文中的单词具有更高的或更低的优先级。

由于较长的文档更有可能包含查询词,因此考虑文档大小是合理的,例如,包含五个搜索词实例的百字文档可能比包含五个搜索词实例的千字文档更相关。两个排名函数都接受一个整数 normalization 选项,该选项指定文档长度如何影响其排名。整数选项控制多种行为,因此它是一个位掩码:您可以使用 | 指定一个或多个行为(例如,2|4)。

  • 0(默认值)忽略文档长度

  • 1 除以 1 + 文档长度的对数

  • 2 除以文档长度

  • 4 除以 extents 之间的平均调和距离(这仅由 ts_rank_cd 实现)

  • 8 除以文档中唯一单词的数量

  • 16 除以 1 + 文档中唯一单词数量的对数

  • 32 除以其本身 + 1

如果指定了多个标志位,则转换按列出的顺序应用。

需要注意的是,排名函数不使用任何全局信息,因此无法产生所需的 1% 或 100% 的公平标准化。可以应用标准化选项 32 (rank/(rank+1))将所有排名缩放到零到一的范围,但这只是一个外观上的更改;它不会影响搜索结果的顺序。

这是一个仅选择排名最高的十个匹配项的示例:

SELECT title, ts_rank_cd(textsearch, query) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |   rank
-----------------------------------------------+----------
 Neutrinos in the Sun                          |      3.1
 The Sudbury Neutrino Detector                 |      2.4
 A MACHO View of Galactic Dark Matter          |  2.01317
 Hot Gas and Dark Matter                       |  1.91171
 The Virgo Cluster: Hot Plasma and Dark Matter |  1.90953
 Rafting for Solar Neutrinos                   |      1.9
 NGC 4650A: Strange Galaxy and Dark Matter     |  1.85774
 Hot Gas and Dark Matter                       |   1.6123
 Ice Fishing for Cosmic Neutrinos              |      1.6
 Weak Lensing Distorts the Universe            | 0.818218

这是使用标准化排名的相同示例:

SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE  query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |        rank
-----------------------------------------------+-------------------
 Neutrinos in the Sun                          | 0.756097569485493
 The Sudbury Neutrino Detector                 | 0.705882361190954
 A MACHO View of Galactic Dark Matter          | 0.668123210574724
 Hot Gas and Dark Matter                       |  0.65655958650282
 The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
 Rafting for Solar Neutrinos                   | 0.655172410958162
 NGC 4650A: Strange Galaxy and Dark Matter     | 0.650072921219637
 Hot Gas and Dark Matter                       | 0.617195790024749
 Ice Fishing for Cosmic Neutrinos              | 0.615384618911517
 Weak Lensing Distorts the Universe            | 0.450010798361481

排名可能很昂贵,因为它需要查询每个匹配文档的 tsvector,这可能受 I/O 限制,因此速度较慢。不幸的是,这几乎是不可避免的,因为实际查询通常会产生大量匹配项。

12.3.4. 高亮显示结果 #

为了呈现搜索结果,最好显示每个文档的一部分以及它与查询的关系。通常,搜索引擎会显示带有标记搜索词的文档片段。PostgreSQL 提供了一个 ts_headline 函数来实现此功能。

ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text

ts_headline 接受一个文档和一个查询,并返回文档的摘录,其中查询中的词被高亮显示。具体来说,该函数将使用查询来选择相关的文本片段,然后高亮显示查询中出现的所有单词,即使这些单词的位置不符合查询的限制。要使用的用于解析文档的配置可以通过 config 指定;如果省略 config,则使用 default_text_search_config 配置。

如果指定了 options 字符串,则它必须由一个或多个 option=value 对组成的逗号分隔列表组成。可用的选项是:

  • MaxWordsMinWords(整数):这些数字决定了输出的标题的最长和最短长度。默认值分别为 35 和 15。

  • ShortWord(整数):此长度或更短的单词将在标题的开头和结尾被删除,除非它们是查询词。默认值 3 会排除常见的英语冠词。

  • HighlightAll(布尔值):如果为 true,则整个文档将用作标题,忽略前面的三个参数。默认值为 false

  • MaxFragments(整数):要显示的文本片段的最大数量。默认值零选择一种非基于片段的标题生成方法。大于零的值选择基于片段的标题生成(如下所述)。

  • StartSelStopSel(字符串):用于分隔文档中出现的查询词的字符串,以将它们与摘录的其他单词区分开。默认值是 <b></b>,这对于 HTML 输出来说是合适的(但请参阅下面的警告)。

  • FragmentDelimiter(字符串):当显示多个片段时,片段将由此字符串分隔。默认值为 ...

警告:跨站脚本(XSS)安全

ts_headline 输出的内容不能保证可以直接包含在网页中。当 HighlightAllfalse(默认值)时,会从文档中删除一些简单的 XML 标签,但这不能保证删除所有 HTML 标记。因此,在处理不受信任的输入时,这不能有效地防范跨站脚本(XSS)等攻击。为了防范此类攻击,应从输入文档中删除所有 HTML 标记,或者在输出时使用 HTML 清理器。

这些选项名称区分大小写。如果字符串值包含空格或逗号,则必须用双引号括起来。

在非基于片段的标题生成中,ts_headline 会查找给定 query 的匹配项,并选择一个进行显示,优先选择在允许的标题长度内具有更多查询词的匹配项。在基于片段的标题生成中,ts_headline 会查找查询匹配项,并将每个匹配项分割成最多 MaxWords 个单词的 片段,优先选择包含更多查询词的片段,并在可能的情况下 扩展 片段以包含周围的单词。因此,当查询匹配项跨越文档的大部分区域,或者希望显示多个匹配项时,基于片段的模式更有用。在这两种模式下,如果无法识别任何查询匹配项,则会显示文档前 MinWords 个单词的一个片段。

例如

SELECT ts_headline('english',
  'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
  to_tsquery('english', 'query & similarity'));
                        ts_headline
------------------------------------------------------------
 containing given <b>query</b> terms                       +
 and return them in order of their <b>similarity</b> to the+
 <b>query</b>.

SELECT ts_headline('english',
  'Search terms may occur
many times in a document,
requiring ranking of the search matches to decide which
occurrences to display in the result.',
  to_tsquery('english', 'search & term'),
  'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');
                        ts_headline
------------------------------------------------------------
 <<Search>> <<terms>> may occur                            +
 many times ... ranking of the <<search>> matches to decide

ts_headline 使用原始文档,而不是 tsvector 摘要,因此它可能速度较慢,应谨慎使用。

提交更正

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