ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 12.4\. 附加功能 本节描述了连接文本搜索中有用的附加功能和操作符。 ## 12.4.1\. 操作文档 节[Section 12.3.1](#calibre_link-1107)显示了原始文本文档如何转换成`tsvector`值。 PostgreSQL也提供用于操作已经在`tsvector`形式中的文档的函数和操作符。 `tsvector` || `tsvector` `tsvector`连接操作符返回一个连接词的向量,以及作为参数给定的2个向量的位置信息。 在连接期间重新获得位置和权重标签。出现在右边向量位置通过左边向量提到的最大位置相抵消, 因此这个结果几乎等同于2个原始文档字符串连接中执行`to_tsvector`的结果。(这个等价是不准确的, 因为任何从左边参数中删除的屏蔽词不会影响结果,然而,如果使用文本连接,它们影响右边参数词的位置)。 使用级联中的向量形式而不是在应用`to_tsvector`之前连接文本的一个优势是, 你可以使用不同的配置解析文档的不同部分。同时,由于`setweight`函数标记所有相同方式给定向量的词汇, 解析文本是必要的,并且如果你想用不同的权重标记文档不同部分,连接前做`setweight`。 `setweight(``_vector_` `tsvector`, `_weight_` `"char"`) returns `tsvector` `setweight` 返回一个输入向量的拷贝,其中每一个位置用给定的`_weight_`, `A`, `B`, `C`或者 `D`之一进行标记。(`D`是缺省新向量,因此不显示在输出上。)当向量连接时,保留这些标签, 允许一个文档的不同部分的词通过不同相关函数加权。 注意权重标签适用于_位置_,不是_词汇_。如果输入向量已经被剥夺了位置,则`setweight`不做任何事情。 `length(``_vector_` `tsvector`) returns `integer` 返回存储在向量中的词的数量。 `strip(``_vector_` `tsvector`) returns `tsvector` 返回一个向量,其中列出了给定向量的同一词,但它缺乏任何位置和权重信息。 虽然为相关性排序返回的向量比一个未拆分向量用处少,它通常会小得多。 ## 12.4.2\. 处理查询 节[Section 12.3.2](#calibre_link-1108)显示了原始文本查询如何转换成`tsquery`值。 PostgreSQL也提供了函数和操作符用于处理已存在`tsquery`形式中的查询 `tsquery` && `tsquery` 返回两个给定查询的与组合。 `tsquery` || `tsquery` 返回两个给定查询的或组合。 `!!` `tsquery` 返回给定查询的反面(非)。 `numnode(``_query_` `tsquery`) returns `integer` 返回在一个`tsquery`中节点的数目(词加操作符)。决定`_query_`是否有意义(返回> 0), 或只包含屏蔽词(返回0),这个函数是很有用的。例子: ``` SELECT numnode(plainto_tsquery('the any')); NOTICE: query contains only stopword(s) or doesn't contain lexeme(s), ignored numnode --------- 0 SELECT numnode('foo & bar'::tsquery); numnode --------- 3 ``` `querytree(``_query_` `tsquery`) returns `text` 返回可用于搜索索引的`tsquery`部分。此函数对检测未索引查询是有帮助的,例如那些只包含屏蔽词或否定术语。比如: ``` SELECT querytree(to_tsquery('!defined')); querytree ----------- ``` ### 12.4.2.1\. 查询重写 函数族`ts_rewrite`搜索一个特定的目标查询事件`tsquery`,和替换每个替代子查询。 实际上这个操作是一个子字符串替换的`tsquery`-特定版本。目标和替换组合可以被认为是一个_查询重写规则_。 一组这样的重写规则可以是一个强大的搜索帮助。例如,你可以使用同义词扩大搜索(例如,`new york`, `big apple`, `nyc`, `gotham`)或缩小搜索一些热点问题的直接用户。在这些特性和同义词词典之间功能上有一些重叠(节[Section 12.6.4](#calibre_link-1121))。然而, 你可以在不重建索引情况下即时修改重写规则,而更新词库需要重建索引才能有效。 `ts_rewrite (``_query_` `tsquery`, `_target_` `tsquery`, `_substitute_` `tsquery`) returns `tsquery` `ts_rewrite`的这种形式只适用于一个单一的重写规则:无论出现在`_query_`的什么地方,`_target_`通过`_substitute_`替换。比如: ``` SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery); ts_rewrite ------------ 'b' & 'c' ``` `ts_rewrite (``_query_` `tsquery`, `_select_` `text`) returns `tsquery` `ts_rewrite`的这种形式接受起始`_查询_`和SQL `_select_`命令,这是作为一个文本字符串。 `_select_`必须产生两列`tsquery`类型。`_select_`结果的每一行,出现的第一个字段的值(目标) 都被当前的`_query_`值中的第二个字段值(替代)。比如: 注意,当多个重写规则适用于这种方式时,应用的顺序非常重要; 因此在实践中你将需要源查询为`ORDER BY`一些排序关键字。 让我们考虑下现实生活中天文的例子。我们将使用表驱动的重写规则扩大查询`supernovae`: ``` CREATE TABLE aliases (t tsquery primary key, s tsquery); INSERT INTO aliases VALUES(to_tsquery('supernovae'), to_tsquery('supernovae|sn')); SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases'); ts_rewrite --------------------------------- 'crab' & ( 'supernova' | 'sn' ) ``` 我们可以通过更新表改变重写规则: ``` UPDATE aliases SET s = to_tsquery('supernovae|sn & !nebulae') WHERE t = to_tsquery('supernovae'); SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases'); ts_rewrite --------------------------------------------- 'crab' & ( 'supernova' | 'sn' & !'nebula' ) ``` 当有许多的重写规则的时候,重写比较缓慢,因为它检查可能匹配的每一个规则。 为过滤掉明显非候选规则,我们可以使用`tsquery`类型的包含操作符。在下面的例子中, 我们只选择那些可能与原始查询匹配的规则: ``` SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t'); ts_rewrite ------------ 'b' & 'c' ``` ## 12.4.3\. 自动更新的触发器 当使用单独的列存储文档的`tsvector`形式,当文档内容列变化时, 有必要建立一个触发器更新`tsvector`列。两个内置的触发器功能可用于此, 或者你可以自定义触发器。 ``` tsvector_update_trigger(_tsvector_column_name_, _config_name_, _text_column_name_ [, ... ]) tsvector_update_trigger_column(_tsvector_column_name_, _config_column_name_, _text_column_name_ [, ... ]) ``` 这些触发器函数自动计算来自一个或多个文本字段的`tsvector`列,在`CREATE TRIGGER`命令指定的参数控制下。 使用的例子是: ``` CREATE TABLE messages ( title text, body text, tsv tsvector ); CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON messages FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(tsv, 'pg_catalog.english', title, body); INSERT INTO messages VALUES('title here', 'the body text is here'); SELECT * FROM messages; title | body | tsv ------------+-----------------------+---------------------------- title here | the body text is here | 'bodi':4 'text':5 'titl':1 SELECT title, body FROM messages WHERE tsv @@ to_tsquery('title & body'); title | body ------------+----------------------- title here | the body text is here ``` 创建触发器,在`title`或者`body`中的任何改变都会自动反映到`tsv`中,而不必担心它的应用。 第一个触发器参数必须是被更新的`tsvector`字段名。第二个参数指定要进行转换的文本搜索配置。 为`tsvector_update_trigger`,配置的名称仅仅是作为第二个触发器参数。它必须是如上所示的模式匹配, 因此触发器的行为在`search_path`中不会改变。为`tsvector_update_trigger_column`, 第二个触发器参数是另一个表列的名称,它的类型必须是`regconfig`。这允许每行选择进行配置。 剩余的参数(s)是文本列的名称(键入`text`, `varchar`或者`char`)。这些将在给定的顺序中提供文档。 空值将被忽略(但其他列仍将被索引)。 这些内置触发器的限制是它们一致对待所有输入列。为了处理不同列—比如, 为权重不同主体的标题—它有必要编写一个自定义触发器。 这是使用PL/pgSQL 作为触发器语言的一个例子: ``` CREATE FUNCTION messages_trigger() RETURNS trigger AS $$ begin new.tsv := setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') || setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D'); return new; end $$ LANGUAGE plpgsql; CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON messages FOR EACH ROW EXECUTE PROCEDURE messages_trigger(); ``` 记住当在触发器内部创造`tsvector`值时,明确指定配置的名称是很重要的, 所以,该列的内容将通过改变`default_text_search_config`而不会受到影响。 如果不这样做可能会导致诸如重载转储之后搜索结果改变的问题。 ## 12.4.4\. 收集文献统计 函数`ts_stat`可用于检查你的配置和查找屏蔽候选词。 ``` ts_stat(_sqlquery_ text, [ `_weights_` `text`, ] OUT _word_ text, OUT _ndoc_ integer, OUT _nentry_ integer) returns setof record ``` `_sqlquery_`是一个包含返回单独`tsvector`列的SQL查询的文本值。 `ts_stat`执行查询并返回包含`tsvector`数据的各个不同的语义(词)的统计。返回的列: * `_word_` `text` — 一个词的值 * `_ndoc_` `integer` —这个词出现的文档编号(`tsvector`s) * `_nentry_` `integer` —这个词出现的总数 如果提供`_weights_` ,仅仅计算这些权重之一。 例如,在一个文档集合中查找十个最常用的单词: ``` SELECT * FROM ts_stat('SELECT vector FROM apod') ORDER BY nentry DESC, ndoc DESC, word LIMIT 10; ``` 同样的,但是只计算权重weight `A`或者`B`的单词: ``` SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab') ORDER BY nentry DESC, ndoc DESC, word LIMIT 10; ```