Postgresql 中文操作指南

12.4. Additional Features #

本节描述了与文本搜索相关的其他函数和运算符。

12.4.1. Manipulating Documents #

Section 12.3.1 显示了如何将原始文本文档转换为 tsvector 值。PostgreSQL 还提供可用于处理已呈 tsvector 形式的文档的函数和运算符。

  • tsvector || tsvector

    • tsvector 连接运算符返回一个向量,它将作为参数给出的两个向量的词素和位置信息结合在一起。在连接期间将保留位置和权重标签。出现在右侧向量的出现位置将偏移左侧向量中提到的最大位置,因此,该结果几乎等同于对两个原始文档字符串的连接执行 to_tsvector 的结果。(等价性并非精确,因为从左侧参数的末尾移除的任何停用词不会影响结果,而如果使用文本连接,则它们会影响右侧参数中词素的位置。)

    • 以向量形式使用连接而不是在应用 to_tsvector 之前连接文本的一个优点是,你可以使用不同的配置来解析文档的不同部分。此外,因为 setweight 函数以同一种方式标记给定向量的全部词素,所以,如果你希望使用不同的权重标记文档的不同部分,则在连接之前必须解析文本并执行 setweight

  • setweight(_vector tsvectorweight "char" ) 返回 tsvector_

    • setweight 返回一个输入向量的副本,其中每个位置都已使用给定的 weight(可以是 ABCD)标记。在连接向量时保留这些标签,从而可以按排名函数对来自文档不同部分的单词赋予不同的权重。

    • 请注意,权重标签适用于 positions,而不适用于 lexemes。如果输入向量已删除位置,则 setweight 没有任何作用。

  • length(_vector tsvector) returns integer_

    • 返回存储在向量中的词素数。

  • strip(_vector tsvector) returns tsvector_

    • 返回一个向量,它列出与给定向量相同的词素,但缺少任何位置或权重信息。结果通常比未剥离的向量小得多,但它也不那么有用。相关性排名在剥离的向量上工作得不像在未剥离的向量上那么好。此外, <→ (FOLLOWED BY) tsquery 运算符永远不会匹配剥离的输入,因为它无法确定词素出现之间的距离。

可以在 Table 9.43 中找到 tsvector 相关函数的完整列表。

12.4.2. Manipulating Queries #

Section 12.3.2 显示了如何将原始文本查询转换为 tsquery 值。PostgreSQL 还提供可用于处理已呈 tsquery 形式的查询的函数和运算符。

  • tsquery && tsquery

    • 返回给定两个查询的 AND 组合。

  • tsquery || tsquery

    • 返回给定两个查询的 OR 组合。

  • !! _tsquery_

    • 返回给定查询的否定值(NOT)。

  • tsquery <→ tsquery

    • 返回一个查询,它使用 &lt;&#8594; (FOLLOWED BY) tsquery 运算符搜索与第一个给定查询的匹配项紧跟与第二个给定查询的匹配项。例如:

SELECT to_tsquery('fat') <-> to_tsquery('cat | rat');
          ?column?
----------------------------
 'fat' <-> ( 'cat' | 'rat' )
  • tsquery_phrase(_query1 tsqueryquery2 tsquery [, distance integer ]) 返回 tsquery_

    • 返回一个查询,它使用 &lt;_N >_ tsquery 运算符搜索与第一个给定查询的匹配项紧跟与第二个给定查询的匹配项,距离恰好为 distance 个词素。例如:

SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10);
  tsquery_phrase
------------------
 'fat' <10> 'cat'
  • 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
-----------
 'defin'

SELECT querytree(to_tsquery('!defined'));
 querytree
-----------
 T

12.4.2.1. Query Rewriting #

ts_rewrite 函数系列搜索给定的 tsquery 中是否出现目标子查询,并使用替代子查询替换每次出现。从本质上讲,此操作是子串替换的 tsquery 专用版本。可以将目标和替代组合视为 query rewrite rule。此类重写规则的集合可能是一个强大的搜索辅助工具。例如,你可以使用同义词(例如,new yorkbig applenycgotham)来扩展搜索,或缩小搜索范围以将用户引导至某些热门话题。此功能与同义词库 ( Section 12.6.4) 之间有一些功能重叠。但是,你可以即时修改一组重写规则而无需重新索引,而更新同义词库则需要重新索引才能生效。

  • ts_rewrite (_query tsquerytarget tsquerysubstitute tsquery ) 返回 tsquery_

    • 此形式的 ts_rewrite 只应用一条重写规则:targetquery 中出现的任何位置都替换为 substitute。例如:

SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);
 ts_rewrite
------------
 'b' & 'c'
  • ts_rewrite (_query tsqueryselect text ) 返回 tsquery_

    • 此形式的 ts_rewrite 接受一个起始 query 和一条 SQL select 命令,该命令作为文本字符串提供。select 必须生成两列 tsquery 类型。对于 select 结果的每一行,第一列值(目标)的出现都会在当前 query 值中替换为第二列值(替换)。例如:

CREATE TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');

SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases');
 ts_rewrite
------------
 'b' & 'c'
  • 请注意,当以这种方式应用多条重写规则时,应用顺序非常重要;因此,在实践中,你会希望源查询 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. Triggers for Automatic Updates #

Note

本节中描述的方法已因使用存储的生成列而失效,如 Section 12.2.2所述。

当使用一个单独的列来存储文档的 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 [, ... ])

这些触发器函数根据 CREATE TRIGGER 命令中指定的参数,自动从一个或多个文本列计算出一个 tsvector 列。以下是一个使用方法的示例:

CREATE TABLE messages (
    title       text,
    body        text,
    tsv         tsvector
);

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE FUNCTION
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

创建此触发器后,titlebody 中的任何更改都将自动反映到 tsv 中,而应用程序不必担心这一点。

第一个触发器参数必须是要更新的 tsvector 列的名称。第二个参数指定要用于执行转换的文本搜索配置。对于 tsvector_update_trigger,配置名称只作为第二个触发器参数给出。它必须如上文所示经过模式限定,以便触发器行为不会因 search_path 的更改而更改。对于 tsvector_update_trigger_column,第二个触发器参数是另一张表列的名称,该列必须是 regconfig 类型。这允许进行每行配置选择。其余参数是文本列的名称(textvarcharchar 类型)。这些将按照所给顺序包括在文档中。NULL 值将被跳过(但仍会对其他列进行索引)。

这些内置触发器的限制在于它们将所有输入列都视为相同。要以不同的方式处理列,例如对标题的权重与正文不同,需要编写一个自定义触发器。这里有一个使用 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 FUNCTION messages_trigger();

请记住,在触发器中创建 tsvector 值时,显式指定配置名称非常重要,以便列的内容不会受 default_text_search_config 中更改的影响。如果不这样做,很可能会导致问题,例如,在转储和还原后搜索结果会更改。

12.4.4. Gathering Document Statistics #

函数 ts_stat 对于检查配置以及查找停用词候选非常有用。

ts_stat(sqlquery text, [ weights text, ]
        OUT word text, OUT ndoc integer,
        OUT nentry integer) returns setof record

sqlquery 是包含 SQL 查询的文本值,该查询必须返回单个 tsvector 列。ts_stat 执行查询,并返回 tsvector 数据中包含的每个不同词素(单词)的统计信息。返回的列是

如果提供了 weights,则只计入具有其中一种权重的出现。

例如,要查找文档集合中最频繁出现的十个单词:

SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

同样,但仅计算带权重的单词出现次数 AB:

SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;