2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本: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 将文本文档解析为标记,将标记简化为词素,并返回一个 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 不同,它也识别某些运算符。此外,此函数永远不会引发语法错误,这使得可以使用原始用户提供的输入进行搜索。支持以下语法

  • unquoted text:引号外的文本将转换为用 & 运算符分隔的术语,就像由 plainto_tsquery 处理一样。

  • "quoted text":引号内的文本将转换为用 <-> 运算符分隔的术语,就像由 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 在 1999 年的《信息处理与管理》杂志上发表的“一至三项查询的相关性排名”中所述。覆盖密度类似于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}

通常,权重用于标记来自文档特殊区域(如标题或初始摘要)的词语,因此可以比文档正文中的词语赋予更多或更少的权重。

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

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

  • 1 将排名除以 1 加上文档长度的对数

  • 2 将排名除以文档长度

  • 4 将排名除以范围之间的平均调和距离(仅由ts_rank_cd 实现)

  • 8 将排名除以文档中唯一词语的数量

  • 16 将排名除以 1 加上文档中唯一词语数量的对数

  • 32 将排名除以自身加 1

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

需要注意的是,排名函数不使用任何全局信息,因此无法生成公平的归一化到 1% 或 100%,正如有时所期望的那样。归一化选项 32 (rank/(rank+1)) 可以应用于将所有排名缩放到 0 到 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(整数):要显示的文本片段的最大数量。默认值 0 选择非基于片段的标题生成方法。大于 0 的值选择基于片段的标题生成(见下文)。

  • StartSelStopSel(字符串):用于分隔文档中出现的查询词的字符串,以将它们与其他摘录词区分开来。默认值为<b></b>,这些值适合 HTML 输出。

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

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

在非基于片段的标题生成中,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 摘要,因此它可能很慢,应该谨慎使用。

提交更正

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