分类:
2008-05-21 15:10:05
|
要实现文本搜索,我们必须有一个函数从文档中创建一个 tsvector,和一个函数从用户的查询中创建 tsquery。还有,我们需要以有用的方式返回结果,因此我们需要一个函数对比文档和查询之间的相关性。把结果显示得比较漂亮也很重要。PostgreSQL 提供了所有这些函数的支持。
PostgreSQL 提供了函数 to_tsvector 用于把文档转换成 tsvector 数据类型。
to_tsvector([ config regconfig, ] document text) returns tsvector
to_tsvector 把一个文本文档分解成记号,把记号分解成语意(lexeme),然后返回一个 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,或 it 这样的单词,单词 rats 变成了 rat,而且标点符号被忽略了。
to_tsvector 函数在内部调用一个分析器,把文档文本分解成记号。然后,对每个记号都进行一系列的词典审核(),这个词典的列表可能和记号的类型密切相关。第一个识别了该记号的词典发出一个或多个规范化的语意(lexeme)以代表该记号。比如,rats 变成了 rat,因为其中一个词典认出单词 rats 是 rat 的复数形式。有些词会别当作屏蔽词看待(),这样它们就会被忽略,因为它们出现得太过频繁,对搜索没什么用处。在我们的例子里,屏蔽词是 a,on,和it。如果列表中的词典都没有识别该记号,那么它也会被忽略。在我们的例子里,标点符号就是这么处理的 - 因为实际上没有什么词典给它们标识记号类型(间隔符号),意味着空间记号永远不会被索引。分析器(分词器),词典和需要索引的记号的选择是由选定的文本搜索配置()决定的。我们可以在同一个数据库里有多个不同的配置,预定义的那些配置可以用于多种语言。在我们的例子里,我们用缺省的配置 english 处理英文。
我们可以使用函数 setweight 来给 tsvector 的项不同的重量,这里的“重量”是A,B,C或者D四个字母之一。这些东西通常用于标记项的是来自于文档的不同部分,比如标题、正文等部分。然后,这些信息可以用于对搜索结构排序。
因为 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 里面标记每个语意(lexeme)的源,然后使用 tsvector 的连接操作符 || 把标记后的 tsvector 值融合起来。(给出了有关这些操作的细节。)
PostgreSQL 提供了函数 to_tsquery 和 plainto_tsquery 用于把查询转换成 tsquery 数据类型。to_tsquery 提供了获取比 plainto_tsquery 更多特性的机制,但是对输入要求更严。
to_tsquery([ config regconfig, ] querytext text) returns tsquery
to_tsquery 从一个查询文本创建一个 tsquery 值,它们必须由使用布尔操作符 & (与),|(或)和 ! (非)分隔的单个记号。这些操作符可以用圆括弧分组。换句话说,给 to_tsquery 的输入必须已经遵守了 tsquery 输入的通用规则,如所示。区别是基本的 tsquery 输入以字面值接受输入的记号,而 to_tsquery 使用指定的或者缺省的配置,把每个记号都规范化成一个语意(lexeme),并且抛弃任何配置里规定的屏蔽词。比如:
SELECT to_tsquery('english', 'The & Fat & Rats'); to_tsquery --------------- 'fat' & 'rat'
和基本的 tsquery 输入一样,我们可以给每个语意(lexeme)附加附加权重以限制它之匹配那些权重的 tsvector 语意。比如:
SELECT to_tsquery('english', 'Fat | Rats:AB'); to_tsquery ------------------ 'fat' | 'rat':AB
to_tsquery 还接受单引号包围的短语。这个功能在配置包含(thesaurus)知识词典、可能触发这类短语的时候很有效。在下面的例子里,一个包含规则 supernovae stars : sn 的知识词典:
SELECT to_tsquery('''supernovae stars'' & !crab'); to_tsquery --------------- 'sn' & !'crab'
如果没有引号,to_tsquery 会生成一个语法错误,因为记号没有被“与”或者“或”操作符分隔。
plainto_tsquery([ config regconfig, ] querytext text) returns tsquery
plainto_tsquery 把未格式化的文本 querytext 转换成 tsquery。文本会像在 to_tsvector 里那样分析和规范化,然后用布尔操作符 & (与)插入到余下的单词中。
例子:
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'
这里,所有输入的标点都被当做间隔符号抛弃。
相关性是企图评判文档与特定的查询之间是多相关的一个衡量标准,这样,如果有很多匹配的项,那么最相关的就会显示在前头。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
对于这两个函数,我们都可选的权重参数提供根据词自身的(权重)标签来加重或者减轻单词的相关性分量。权重数组声明每个级别的单词有多重,按照下面顺序:
{D-weight, C-weight, B-weight, A-weight}
如果没有提供权重,那么使用下面的缺省:
{0.1, 0.2, 0.4, 1.0}
通常来说,权重用于把单词标记为来自于文档的哪个趋于,比如标题和开头的摘要,这样就可以以比文档体更重要或更不重要的方式对待它们。
因为更长的文档有更大的机会包含搜索的词,所以把文档尺寸考虑在内也是很合理的,也就是说,假如一个一百个词的文档里,出现了五个搜索词的实例,那么其相关性要比一千个词的文档出现五个实例要更相关。两个相关性函数都接受一个整数的规范化选项,声明一个文档的长度是否影响相关性,以及如何影响。这个整数选项控制好几个行为,所以它是一个位掩码:你可以用 | 声明一个或多个行为(比如,2|4)。
如果提供了多于一个的标志位,那么变换以列出的顺序施加。
我们要注意的是相关性函数并不使用任何全局信息,所以我们不可能生成一个公平的 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密集型的操作,因此也会比较慢。不幸的是,我们几乎不可能避免这个事情,因为实际的查询总是会有大量的匹配。
为了呈现搜索结果,最好是显示每个文档的一部分以及它和查询之间是如何关联的。通常,搜索引擎显示带有标识出来的搜索词的文档片段。PostgreSQL 提供一个函数 ts_headline 实现了这个功能。
ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text
ts_headline 接受一个文档以及对应的查询,然后返回一个文档的摘要,在摘要里面,查询是高亮的。用于分析文档的配置可以在 config 里面声明;如果忽略 config,那么使用 default_text_search_config 配置。
如果声明了一个可选的字串,那么它必须由以一个逗号分隔的一个或多个“选项=值”对组成。可用的选项有:
任何未声明的选项都会得到下面的缺省值:
StartSel=, StopSel=, MaxWords=35, MinWords=15, ShortWord=3, HighlightAll=FALSE
比如:
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 ------------------------------------------------------------ given query terms and return them in order of their similarity to the query. 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 ------------------------------------------------------- giventerms and return them in order of their to the .
ts_headline 使用原始的文档,而不是 tsvector 摘要,所以它可能比较慢,因此要小心使用。一个常见的错误是对每个匹配的文档都调用 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;