要执行全文搜索,必须有一个函数,用来从文档创建一个 tsvector
,以及从用户查询创建一个 tsquery
。此外,我们需要按有用的顺序返回结果,因此我们需要一个在相关性方面比较文档和查询的函数。能够很好地显示结果也很重要。PostgreSQL 支持所有这些函数。
PostgreSQL 提供函数 to_tsvector
,用于将文档转换为 tsvector
数据类型。
to_tsvector([config
regconfig
, ]document
text
) returnstsvector
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
或 it
,单词 rats
变为 rat
,标点符号 -
被忽略了。
函数 to_tsvector
在内部调用一个解析器,该解析器将文档文本分解为标记,并为每个标记分配一个类型。对于每个标记,查找词典列表(第 12.6 节),其中的列表可能会根据标记类型而有所不同。第一个识别标记的字典会发出一个或多个规范化词素来表示标记。例如,rats
变为 rat
,因为其中一个词典识别单词 rats
为 rat
的复数形式。有些单词被识别为停止词(第 12.6.1 节),这会导致它们被忽略,因为它们出现的频率太高,以致于在搜索中没有用处。在我们的例子中,这些词是 a
、on
和 it
。如果列表中的任何词典都没有识别出标记,那么该标记也会被忽略。在此示例中,出现这种情况的标点符号是 -
,因为实际上没有为其标记类型(空白符号)分配词典,这意味着空白标记永远不会被编入索引。解析器、词典和要编入索引的标记类型的选择由所选文本搜索配置确定(第 12.7 节)。在同一个数据库中可以有多个不同的配置,并且已为各种语言准备了预定义的配置。在我们的示例中,我们对英语使用了默认配置 english
。
函数 setweight
可用于使用给定权重对 tsvector
的条目进行标记,其中权重是字母 A
、B
、C
或 D
之一。这通常用于标记来自文档不同部分(例如,标题与正文)的条目。之后,此信息可用于对搜索结果进行排名。
由于 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 节 提供了有关这些操作的详细信息。)
PostgreSQL 提供函数 to_tsquery
、plainto_tsquery
、phraseto_tsquery
和 websearch_to_tsquery
,用于将查询转换为 tsquery
数据类型。与 plainto_tsquery
或 phraseto_tsquery
相比,to_tsquery
可访问更多功能,但对输入的要求也更严格。 websearch_to_tsquery
是 to_tsquery
的简化版本,它采用替代语法,类似于网络搜索引擎使用的语法。
to_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
to_tsquery
根据 querytext
创建 tsquery
值,它必须包含用 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'
如果不加引号,to_tsquery
会针对未用 AND、OR 或 FOLLOWED BY 运算符分隔的词章生成语法错误。
plainto_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
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
) returnstsquery
phraseto_tsquery
与 plainto_tsquery
的行为非常相似,不同之处在于它在保留的单词之间插入 <->
(后跟)运算符,而不是 &
(AND)运算符。此外,停用词不会被简单地舍弃,而是通过插入 <
运算符来考虑到它们,而不是 N
><->
运算符。此函数在搜索精确词素序列时很有用,因为后跟运算符不仅检查词素的存在,还检查词素的顺序。
示例
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
) returnstsquery
websearch_to_tsquery
使用一种替代语法从 querytext
创建 tsquery
值,在此语法中,简单的未格式化文本是一个有效的查询。与 plainto_tsquery
和 phraseto_tsquery
不同,它还识别某些运算符。此外,此函数永远不会引发语法错误,这使得可以将未经处理的用户提供的输入用于搜索。支持以下语法
未加引号的文本
:未放在引号内的文本将被转换为由 &
运算符分隔的术语,就像是由 plainto_tsquery
处理的一样。
"加引号的文本"
:放在引号内的文本将被转换为由 <->
运算符分隔的术语,就像是由 phraseto_tsquery
处理的一样。
OR
:单词 “or” 将被转换为 |
运算符。
-
:破折号将被转换为 !
运算符。
其他标点符号将被忽略。因此,与 plainto_tsquery
和 phraseto_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)
排名尝试衡量文档与特定查询的相关程度,以便在匹配项较多时,可以优先显示最相关的项。PostgreSQL 提供了两个预定义的排名函数,它们考虑了词法、邻近和结构信息;也就是说,它们考虑了查询术语在文档中出现的频率、术语在文档中的紧密程度以及术语出现的文档部分的重要性。但是,相关性的概念很模糊,并且非常特定于应用程序。不同的应用程序可能需要额外的排名信息,例如文档修改时间。内置排名函数只是示例。可以编写自己的排名函数和/或将其结果与其他因素相结合,以满足自己的特定需求。
目前可用的两个排名函数是
ts_rank([ weights
float4[]
, ] vector
tsvector
, query
tsquery
[, normalization
integer
]) 返回 float4
根据匹配词元的频率对向量进行排名。
ts_rank_cd([ weights
float4[]
, ] vector
tsvector
, query
tsquery
[, normalization
integer
]) 返回 float4
此函数计算给定文档向量和查询的覆盖密度排名,如 Clarke, Cormack, 和 Tudhope 在期刊“信息处理与管理”的 1999 年发表的“一到三个词条查询的相关程度排名”中所述。覆盖密度与ts_rank
排名类似,除了考虑匹配词元彼此的接近性。
此函数需要词元位置信息来执行其计算。因此,它会忽略tsvector
中的任何“stripped”词元。如果输入中没有未 stripped 的词元,则结果将为零。(有关strip
函数以及tsvector
中位置信息,请参见第 12.4.1 节。)
对于这两个函数,可选的weights
参数提供根据词条标记方式加大或减轻词条实例权重的功能。权重数组指定按照顺序对每一类词条加大或减轻权重的力度
{D-weight, C-weight, B-weight, A-weight}
如果没有提供weights
,则使用下列默认值
{0.1, 0.2, 0.4, 1.0}
通常会使用权重标记文档中的特定区域,比如标题或摘要开头,以便这些区域比文档正文中的词条重要或不重要。
由于较长的文档更有可能包含查询词条,因此考虑文档长度是合理的,例如,一个 100 字的文档包含 5 个搜索词条实例,可能比一个 1000 字的文档包含 5 个实例更相关。两个排名函数均使用一个整数normalization
选项来指定文档的长度对其排名的影响方式。整数选项控制多个行为,所以这是一个位掩码:可以使用|
来指定一个或多个行为(例如,2|4
)。
0(默认)忽略文档长度
1 将排名除以 1 加上文档长度的对数
2 将排名除以文档长度
4 将排名除以范围之间的平均谐波距离(仅 ts_rank_cd
实现这个)
8 将排名除以文档中的词语的唯一数
16 将排名除以 1 + 文档中唯一词语的对数
32 将排名除以其本身 + 1
如果指定了多个 flag 位,将按照所列顺序应用变换。
请务必注意,排名函数不使用任何全局信息,因此无法按要求生成公平的 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 影响,因此执行起来很慢。不幸的是,几乎无法避免这种情况,因为实际查询通常会导致大量的匹配项。
为了展示搜索结果,最好显示每份文档的一部分并说明它与查询的关系。通常,搜索引擎会展示带标记搜索词的文档片段。PostgreSQL 提供了一个实现此功能的函数 ts_headline
。
ts_headline([config
regconfig
, ]document
text
,query
tsquery
[,options
text
]) returnstext
ts_headline
接受文档和查询,并返回一个文档中的摘录,其中来自查询的词语会突出显示。具体地说,该函数将使用查询选择相关文字片段,然后突出显示出现在查询中的所有词语,即使这些词语的位置与查询的限制不符。可由 config
指定用于解析文档的配置;如果省略了 config
,则使用 default_text_search_config
配置。
如果指定了 options
字符串,则它必须由一个或多个 option
=
value
逗号分隔对组成。可用选项有
MaxWords
、MinWords
(整数):这些数字确定要输出的最长最短标题。默认值为 35 和 15。
ShortWord
(整数):长度小于或等于此值的词语将从标题的开头和结尾处删除,除非它们是查询词语。默认值是 3,可消除常见的英语冠词。
HighlightAll
(布尔):如果 true
,则将使用整个文档作为标题,忽略前面三个参数。默认值为 false
。
MaxFragments
(整数):显示的文本片段的最大数量。零的默认值会选择一个不基于片段的标题生成方法。大于零的值选择基于片段的标题生成(见下文)。
StartSel
,StopSel
(字符串):用于分隔文档中出现的查询单词的字符串,以将它们与其他摘录单词区分开来。默认值为 “<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
摘要,所以它可能会很慢,应谨慎使用。