【发布时间】:2015-10-26 17:12:40
【问题描述】:
我是 Postgresql 的新手,所以如果我的问题没有意义,请多多包涵。
我正在尝试找到一种将我的数据库结构迁移到 Postgresql 的方法,特别是,我发现函数非常方便,并且希望使我的众多触发器更易于编写。
在我的数据库中,我使用标准的 last_modified 和 last_modified_by 字段来跟踪更改。我还使用带有增量序列的标准主键。
有将序列链接到主键 id 的内置语法,但由于我必须为 last_modified 字段编写触发器,所以我想知道是否可以有一个通用函数来一次更新所有内容。
示例:
表ANIMAL 具有字段AMIMAL_ID(主键,具有序列SEQ_ANIMAL),字段LAST_MODIFIED 和LAST_MODIFIED_BY。
同样,我有一个表 PLANT 与字段 PLANT_ID(主键,与序列 SEQ_PLANT),字段 LAST_MODIFIED 和 LAST_MODIFIED_BY 再次。
我想创建一个通用函数,以便在我需要创建的 4 个触发器中调用。我希望得到这样的东西:
插入函数前:
CREATE FUNCTION TRIGGER_BI(p_pkField text, p_Sequence text) RETURNS TRIGGER AS $$
DECLARE
curtime timestamp := now();
BEGIN
NEW.LAST_UPDATED := curtime;
NEW.LAST_UPDATED_BY := current_user;
NEW.p_pkField := nextval(p_Sequence);
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
更新前功能:
CREATE FUNCTION TRIGGER_BU() RETURNS TRIGGER AS $$
DECLARE
curtime timestamp := now();
BEGIN
NEW.LAST_UPDATED := curtime;
NEW.LAST_UPDATED_BY := current_user;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
现在,表ANIMAL 的触发器:
插入前:
CREATE TRIGGER ANIMAL
BEFORE INSERT
FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BI("ANIMAL_ID", "SEQ_ANIMAL");
更新前:
CREATE TRIGGER ANIMAL
BEFORE UPDATE
FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BU();
表PLANT的触发器:
插入前:
CREATE TRIGGER PLANT
BEFORE INSERT
FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BI("PLANT_ID", "SEQ_PLANT");
更新前:
CREATE TRIGGER PLANT
BEFORE UPDATE
FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BU();
是否有可能以这种方式获得通用的东西?
是的! 正确的语法是什么? 奖励:事件可能有一个函数来完成所有工作,默认为空参数,如果为空,则不会更新序列。
是的,但是等等!这种方法的缺点是什么? (性能、安全性,还有什么需要考虑的)?
不!所以我真的需要每个触发器的函数吗?
更新: 我明确地创建了序列,因为我可能希望在多个表之间拥有 共享 序列。这个想法是将共享序列用作唯一的父表,其中几个子表在其父表的主键上具有外键。不要犹豫评论这种方法,但我的基本理解是访问序列的下一个值比必须管理外键更有效......
更新 2:
我发现了一些非常有趣的东西,几乎可以让我到达那里 - 只是我的 setValue 函数不起作用......
这里是通用触发器:
CREATE OR REPLACE FUNCTION TRIGGER_FUNC() RETURNS TRIGGER AS $$
DECLARE
p_pkField text;
p_Sequence text;
pkValue int;
BEGIN
EXECUTE format('SELECT ($1).%I::int', TG_ARGV[0]) USING NEW INTO pkValue;
p_Sequence := quote_ident(TG_ARGV[1]);
IF pkValue IS NULL THEN
SELECT setfieldValue(pg_typeof(NEW), TG_ARGV[0], nextval(p_Sequence));
END IF;
NEW.LAST_UPDATED := curtime;
NEW.LAST_UPDATED_BY := current_user;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
我找到了setValue 函数here 解决方案的提示,并尝试对其进行调整,但它不起作用——我只是使用了错误的调用吗?或者我可以在方法中使用一些额外的知识来使其更简单吗? (我已经使用了我正在设置 bigint 值的事实,但我也许可以做得更好?!)
这里是(非工作)代码:
CREATE OR REPLACE FUNCTION public.setfieldValue(anyelement, text, bigint)
RETURNS anyelement
LANGUAGE plpgsql
AS $function$
DECLARE
_name text;
_values text[];
_value text;
_attnum int;
BEGIN
FOR _name, _attnum
IN SELECT a.attname, a.attnum
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = (SELECT typrelid
FROM pg_type
WHERE oid = pg_typeof($1)::oid)
LOOP
IF _name = $2 THEN
_value := $3;
ELSE
EXECUTE 'SELECT (($1).' || quote_ident(_name) || ')::text' INTO _value USING $1;
END IF;
_values[_attnum] := COALESCE('"' || replace(replace(_value, '"', '""'), '''', '''''') || '"', '');
END LOOP;
EXECUTE 'SELECT (' || quote_ident(pg_typeof($1)::text) || ' ''(' || array_to_string(_values,',') || ')'').*' INTO $1;
RETURN $1;
END;
$function$;
【问题讨论】:
-
触发器对我来说看起来不错。当你尝试它们时发生了什么?
-
您是否有多个用户使用不同的数据库凭据连接?还是
CURRENT_USER只是为了演示? (也就是说,你有真正的用户连接到数据库还是只有应用程序?) -
考虑只有应用程序正在连接,而 CURRENT_USER 实际上是使用该应用程序的用户(我仍然不知道我是否想为每个应用程序用户创建一个 DB 用户并处理 DB 用户的登录和凭据,或者如果我将以某种方式将用户名传递给查询)。
-
我不是正统的,我真的很喜欢编程的新鲜视角。但是,您的想法似乎并不恰当。外键主要用于控制参照完整性。内置机制以最好的方式实现它,而在你的方法中我看不到这种控制。很难想出比多年来开发并经过数百万次测试的解决方案更好的解决方案,尤其是在主键和外键等基本功能方面。
标签: postgresql triggers primary-key