受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

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 在内部会调用解析器,把文档文本拆分成词元并为每类词元分配类型。对于每个词元,系统都会查询一个词典列表(Section 12.6),而这个列表会随词元类型而变化。第一个能够识别该词元的词典,会输出一个或多个正规化后的词位来表示它。例如,rats 之所以变成 rat,是因为某个词典识别出 ratsrat 的复数形式。某些词会被识别为停用词Section 12.6.1),于是被忽略,因为它们出现得过于频繁,对搜索没有帮助。在这个示例中,aonit 就是停用词。如果列表中的词典都无法识别某个词元,它也会被忽略。示例里的标点符号 - 就属于这种情况,因为其词元类型(Space symbols)实际上没有分配任何词典,也就是说,空白类词元永远不会被索引。解析器、词典以及需要索引哪些词元类型,都由所选的文本搜索配置(Section 12.7)决定。一个数据库里可以同时存在多种不同配置,并且系统已经为多种语言提供了预定义配置。在本例中,我们使用的是英语的默认配置 english

setweight 函数可用于给 tsvector 中的项打上指定的权重标签,权重可以是四个字母之一:ABCD。这通常用于标记来自文档不同部分的项,例如标题和正文。稍后,这些信息可以用于搜索结果排名。

由于 to_tsvector(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 值合并起来(关于这些操作的细节,见 Section 12.4.1)。

12.3.2. 解析查询 #

PostgreSQL 提供了 to_tsqueryplainto_tsqueryphraseto_tsquery 以及 websearch_to_tsquery,用于把查询转换成 tsquery 数据类型。to_tsquery 提供的特性比 plainto_tsqueryphraseto_tsquery 更丰富,但对输入也更严格。websearch_to_tsquery 则是 to_tsquery 的简化版本,使用一种类似 Web 搜索引擎的替代语法。

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

to_tsquery 根据 querytext 创建一个 tsquery 值。输入必须由单个词元组成,这些词元之间使用 tsquery 操作符 &(AND)、|(OR)、!(NOT)和 <->(FOLLOWED BY)分隔,并可使用圆括号分组。换句话说,传给 to_tsquery 的输入,必须已经遵循 Section 8.11.2 中描述的 tsquery 一般输入规则。不同之处在于,基础 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处理一样。

  • ORor将转换为|操作符。

  • -:短横线会被转换成 ! 操作符。

其他标点符号都会被忽略。因此,与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 年发表于期刊 Information Processing and Management 的文章 Relevance Ranking for One to Three Term Queries。覆盖密度排名与 ts_rank 类似,但还会考虑匹配词位彼此之间的接近程度。

该函数需要词位位置信息才能完成计算。因此,它会忽略 tsvector 中任何被剥离的词位。如果输入中根本没有未剥离的词位,结果就会是零(有关 strip 函数以及 tsvector 中位置信息的更多内容,见 Section 12.4.1)。

对这两个函数来说,可选的 weights 参数允许根据词实例的标注情况赋予它们不同权重。权重数组按如下顺序指定各类词的权重:

{D-权重, C-权重, B-权重, A-权重}

如果没有提供 weights,则使用如下默认值:

{0.1, 0.2, 0.4, 1.0}

通常,权重用于标记来自文档特定区域的词,例如标题或开头摘要中的词,从而使它们相较于正文中的词被赋予更高或更低的重要性。

由于较长的文档更有机会包含查询词,因此把文档大小纳入考量是合理的。例如,一个一百词的文档里某个搜索词出现五次,通常会比一个一千词的文档里同一搜索词也只出现五次更相关。两种排名函数都接受一个整数 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(字符串):当显示多个片段时,片段之间将以该字符串分隔。默认值是 ...

警告:跨站脚本(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 摘要,因此它可能较慢,使用时应当谨慎。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。