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不包含词 a, on或者 itrats变成rat,并且忽略标点符号-。

to_tsvector函数内部调用一个分析器,将文档文本分解成记号并指定每个标记的类型。 为每个标记,参阅词典列表(节Section 12.6),列表因不同的标记类型而不同。 第一本词典识别标记发出一个或多个标准词汇 表示标记。例如,rats变成rat 因为字典认为词ratsrat的复数形式。有些词被作为屏蔽词(节Section 12.6.1), 这样它们就会被忽略,因为它们出现得太过频繁以致于搜索中没有用处。在我们的例子中,它们是a, onit。 如果列表中没有词典识别标记,那么它也被忽略。在这个例子中,发生在标点符号处-因为事实上没有词典分配给它的标记类型(空间符号), 意味着空间记号永远不会被索引。语法分析器的选择,词典和索引类型的标记是由选定的文本搜索配置决定(节Section 12.7)。 可以在同一个数据库中有多种不同的配置,与提供各种语言的预定义的配置。在我们的例子中,我们使用缺省配置english为英语。

函数setweight可以用于标识一个给定权重tsvector的词条,权重是字母A, B, C或者D之一。通常标记来自文档不同部分的词条,比如标题正文。之后,这些信息可以用于搜索结果的排序。

因为to_tsvector(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_tsquery将查询转换为tsquery数据类型。 to_tsquery提供比plainto_tsquery更多的功能,但对其输入不宽容。

to_tsquery([ `_config_` `regconfig`, ] _querytext_ text) returns tsquery

to_tsquery_querytext_中创建一个tsquery,它必须由布尔运算符& (AND), | (OR)和! (NOT)分离的单个标记组成。这些运算符可以用圆括弧分组。换句话说,to_tsquery输入必须遵循tsquery输入的一般规律,如节Section 8.11所描述的。不同的是当基本tsquery输入以标记表面值的时候,to_tsquery使用指定或默认配置规范每个标记到一个词, 并丢弃所有标记依据配置的屏蔽词。比如:

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

作为基本tsquery输入,权重(s)可以附属于每个词来限制它只匹配那些权重(s)的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'

没有引号,to_tsquery会生成标记语法错误,这个标记不是通过AND 或者OR操作符分离的。

plainto_tsquery([ `_config_` `regconfig`, ] _querytext_ text) returns tsquery

plainto_tsquery变换未格式化的文本_querytext_tsquery。 分析文本并且归一化为to_tsvector,然后在存在的词之间插入&(AND)布尔算子。

比如:

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

请注意,plainto_tsquery无法识别布尔运算符,权重标签,或在其输入中的前缀匹配标签:

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

在这里,所有的输入标点符号作为空格符号丢弃。

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

这个函数计算给定文档向量和查询的覆盖密度相关性,正如1999年在 杂志“信息处理与管理”中Clarke,Cormack和Tudhope的“一至三项查询相关性排序”描述的一样。

这些函数需要位置信息的输入。因此它不能在"剥离"tsvector值的情况下运行—它将总是返回零。

对于这些函数,可选的_weights_参数提供权衡词的情况能力或多或少地取决于它们是如何被标记的。 权重阵列指定顺序权重每类词的频率。

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

如果没有提供_weights_,则利用这些缺省值:

{0.1, 0.2, 0.4, 1.0}

通常的权重是用来标记文档特殊区域的词,如标题或最初的摘要, 所以他们有着比文档主体中的词或多或少的重要性。

由于较长的文档有包含查询词的机会,它合理的考虑文档的大小,例如, 带有搜索词的五个实例的百字文档可能比千字文档有更多的相关性。 相关接受一个整数_normalization_选项,指定文档长度是否以及如何影响它的排序。 整数选项控制一些行为,所以它是一位掩码:您可以使用|(例如,2|4)指定一个或多个行为。

  • 0(缺省)表示跟长度大小没有关系

  • 1 表示关注度(rank)除以文档长度的对数+1

  • 2表示关注度除以文档的长度

  • 4表示关注度除以范围内的平均谐波距离,只能使用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_对组成。可用选项是:

  • StartSel, StopSel:该字符串分隔文档中出现的查询词,以区别于其他摘录词。 如果它们含有空格或逗号,你必须用双引号字符串。

  • MaxWords, MinWords:这些数字决定最长和最短的标题输出。

  • ShortWord:这个长度或更短的词在标题的开始和结束被丢弃。三个默认值消除了常见英语文章。

  • HighlightAll:布尔标志;如果为,整个文档将作为标题,忽略了前面的三个参数。

  • MaxFragments:要显示的文本摘录或片段的最大数量。默认值零选择非片段标题的生成方法。 一个大于零的值选择基于片段的标题生成。此方法查找文本片段与尽可能多的查询词并在查询词周围延伸这些片段。 作为查询词的结果接近每一片段中间,每边都有词。每个片段至多是MaxWords ,并且长度为ShortWord或更短的词在每一个片段开始和结束被丢弃。 如果不是所有的查询词在文档中找到,则文档中开头的MinWords单片段将被显示。

  • FragmentDelimiter:当一个以上的片段显示时,通过字符串分隔这些片段。

任何未声明的选项接受这些缺省:

StartSel=<b>, StopSel=</b>,
MaxWords=35, MinWords=15, ShortWord=3, HighlightAll=FALSE,
MaxFragments=0, FragmentDelimiter=" ... "

比如:

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('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',
  '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('query & similarity'),
  'StartSel = <, StopSel = >');
                      ts_headline                      
-------------------------------------------------------
 containing given <query> terms
 and return them in order of their <similarity> to the
 <query>.

ts_headline使用原始文档,而不是一个tsvector摘要,因此它很慢,应小心使用。 一个典型的错误是,当只显示10个文档时,为每个匹配文档调用ts_headline。SQL子查询可以帮忙, 这是一个例子:

SELECT id, ts_headline(body, q), rank
FROM (SELECT id, body, q, ts_rank_cd(ti, q) AS rank
      FROM apod, to_tsquery('stars') q
      WHERE ti @@ q
      ORDER BY rank DESC
      LIMIT 10) AS foo;