PostgreSQL行级BEFORE触发器函数必须返回NEW或NULL,否则INSERT/UPDATE失败;生成摘要宜用BEFORE INSERT OR UPDATE,配合substring()与正则确保单词完整截断,并注意多语言边界处理。

触发器函数必须返回NEW或NULL,否则INSERT/UPDATE会失败
PostgreSQL触发器函数在BEFORE行级触发时,必须显式返回NEW(修改后行)或NULL(中止操作)。如果忘记RETURN NEW,会导致插入/更新报错:ERROR: trigger function must return a value。这是最常踩的坑。
摘要生成通常发生在写入时,所以用BEFORE INSERT OR UPDATE最合适——你能在数据落盘前动态填充摘要字段,且不增加额外I/O。
- 函数体末尾必须写
RETURN NEW(哪怕没改字段) - 若想拒绝非法内容(比如正文为空),可返回
NULL并配合RAISE EXCEPTION - 不要在函数里做
INSERT INTO ... SELECT这类DML操作——除非声明为SECURITY DEFINER且真有必要
pg_trgm扩展 + substring()是最轻量的摘要提取方式
不用上NLP模型,PostgreSQL原生就能做“合理截断”:对text字段取前N个字符,但要避开单词中间断开。核心是组合substring()和正则匹配,再加pg_trgm支持模糊相似度(如后续做摘要去重)。
示例:从content字段生成最多120字符、以完整单词结尾的摘要:
CREATE OR REPLACE FUNCTION generate_summary()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.content IS NOT NULL AND LENGTH(NEW.content) > 0 THEN
NEW.summary := COALESCE(
substring(NEW.content FROM '^(.{0,120})s'),
left(NEW.content, 120)
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-
substring(... FROM '^(.{0,120})s')匹配“开头起最多120字符+紧跟空格”,确保不劈开单词 -
COALESCE兜底:如果没匹配到空格(比如超长单字词或无空格文本),就用left()硬截 - 别用
split_part()分词再拼——它不处理标点和换行,且性能差
触发器绑定需明确指定触发时机和粒度
触发器本身不执行逻辑,只是“开关”。必须用CREATE TRIGGER把函数挂到具体表的特定事件上,且要写清是FOR EACH ROW还是FOR EACH STATEMENT——摘要生成一定是行级。
假设表叫articles,有content和summary字段:
CREATE TRIGGER trig_generate_summary BEFORE INSERT OR UPDATE OF content ON articles FOR EACH ROW EXECUTE FUNCTION generate_summary();
-
UPDATE OF content表示只在content列被更新时触发,避免无关字段更新也跑函数 - 不要漏掉
FOR EACH ROW——默认是语句级,那NEW不可用,会直接报错 - 如果摘要字段允许为空,函数里记得设
NEW.summary := NULL而非跳过,否则旧值残留
注意字符集与多语言截断的边界问题
中文、日文等无空格语言下,正则s匹配会失效,导致substring()始终返回NULL,最终靠left()硬截——这可能在UTF-8下切坏一个汉字(占3字节)。
更稳的做法是用regexp_replace()配合Unicode词界:
NEW.summary := left(
regexp_replace(NEW.content, E'^(.{0,120})(?:\p{Z}|$).*', E'\1'),
120
);
-
\p{Z}匹配任意分隔符(包括中文全角空格、换行、制表等) - 仍需
left(..., 120)兜底,防止正则没匹配上时返回超长结果 - 确保数据库LC_COLLATE支持Unicode(一般默认即可,但Docker镜像有时用
Clocale,会失效)
真正上线前,拿含中英文混排、emoji、换行符的真实样本测一遍summary输出——边界情况比想象中多。










