我是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$;
答案 0 :(得分:3)
否用于手动设置主键的默认值。
使用主键serial
(或bigserial
)声明您的表,并使用内置机制来处理此类列。
不要担心主键的值不连续。
主键仅用于明确标识行,而不是用于识别行。
除此之外,你不能这样做,因为触发器函数不能有声明的参数。
是用于设置许多表中常用列的值。您可以使用相同的触发器功能进行插入和更新。例如:
CREATE OR REPLACE FUNCTION generic_trigger() -- function without arguments
RETURNS TRIGGER AS $$
BEGIN
NEW.last_modified := now();
NEW.last_modified_by := current_user;
RETURN NEW; -- important!
END;
$$
LANGUAGE 'plpgsql';
create table table_a
(a_id serial primary key, last_modified timestamp, last_modified_by text);
create table table_b
(b_id serial primary key, last_modified timestamp, last_modified_by text);
create trigger table_a_trigger
before insert or update on table_a
for each row execute procedure generic_trigger();
create trigger table_b_trigger
before insert or update on table_b
for each row execute procedure generic_trigger();
insert into table_a default values;
select * from table_a;
a_id | last_modified | last_modified_by
------+-------------------------+------------------
1 | 2015-10-26 19:14:34.642 | postgres
(1 row)
可能你有非常重要的理由在触发器中设置主键的值(请参阅jpmc26的注释)。
在这种情况下,主键应声明为integer
(bigint
)而不是default expression
,触发函数应如下所示:
create or replace function generic_trigger()
returns trigger as $$
begin
new.last_modified := now();
new.last_modified_by := current_user;
if tg_op = 'INSERT' then
if tg_table_name = 'table_a' then
new.a_id:= nextval('table_a_a_id_seq');
elsif tg_table_name = 'table_b' then
new.b_id:= nextval('table_b_b_id_seq');
end if;
end if;
return new;
end;
$$
language 'plpgsql';
insert into table_a (a_id) values (15);
select * from table_a;
a_id | last_modified | last_modified_by
------+-------------------------+------------------
1 | 2015-10-26 19:14:34.642 | postgres
2 | 2015-10-26 21:15:49.243 | postgres
(2 rows)
详细了解Trigger Procedures。
答案 1 :(得分:2)
SERIAL
会更好:
CREATE TABLE animal
(
animal_id SERIAL PRIMARY KEY,
...
);
请注意,SERIAL
实际上只是语法糖。它为您节省了一些麻烦:
DEFAULT
的{{1}}以获取下一个序列值。animal_id
。但是使用NOT NULL
时存在问题。如果某人SERIAL
具有显式值,则将使用该值而不是从序列中生成一个值:
INSERT
如果您的应用程序连接到数据库而不是真实用户,则不必担心这一点;开发人员比明确指定他们想要生成的值更清楚。但是,如果您有真实用户直接登录到数据库,这可能是一个问题。您引用INSERT INTO animal (animal_id, ...) VALUES(50000, ...);
这一事实让我觉得这可能是您的用例。正如您所建议的那样,触发器将解决该问题:
CURRENT_USER
(请注意,如果他们明确尝试设置值,我会抛出错误而不是覆盖。对于您的用户来说,通常更快失败而不是默默地更改他们指定的值。)
不幸的是,您确实需要每个表CREATE TABLE animal (animal_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE SEQUENCE animal_id_seq;
ALTER SEQUENCE animal_id_seq OWNED BY animal.animal_id;
CREATE OR REPLACE FUNCTION generate_animal_id()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.animal_id IS NOT NULL THEN
RAISE EXCEPTION 'Cannot specify animal_id on INSERT: %', NEW.animal_id;
ELSE
NEW.animal_id = NEXTVAL('animal_id_seq'::regclass);
RETURN NEW;
END IF;
END
$$
;
CREATE TRIGGER animal_insert_generate_pk
BEFORE INSERT ON animal
FOR EACH ROW
EXECUTE PROCEDURE generate_animal_id();
,因为每个表都需要使用不同的序列。
让我先谈谈FUNCTION
。是的,你需要一个触发器。您可以为此处的所有表定义单个函数,但是:
UPDATE
离开CREATE OR REPLACE FUNCTION populate_last_updated_columns()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF
NEW.last_updated IS NOT NULL
OR NEW.last_updated_by IS NOT NULL
THEN
RAISE EXCEPTION 'Cannot specify last_updated or last_updated_by: %, %', NEW.last_updated, NEW.last_updated_by;
END IF;
NEW.last_updated = CURRENT_TIMESTAMP;
NEW.last_updated_by = CURRENT_USER;
RETURN NEW;
END;
$$
;
CREATE TRIGGER update_animal
BEFORE UPDATE ON animal
FOR EACH ROW
EXECUTE PROCEDURE populate_last_updated_columns();
CREATE TRIGGER update_plant
BEFORE UPDATE ON plant
FOR EACH ROW
EXECUTE PROCEDURE populate_last_updated_columns();
。同样,这取决于您是否有真实用户连接到数据库并编写查询。如果不,那么您应该利用INSERT
来简化;再次,开发人员足够聪明,不能手动指定这些,只是让他们填写:
DEFAULT
但是如果您有用户直接连接并编写自己的查询,那么使用CREATE TABLE animal
(
...
last_updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_updated_by TEXT NOT NULL DEFAULT CURRENT_USER,
...
);
来阻止他们手动指定这些值可能是有意义的。您可以重用与TRIGGER
触发器相同的功能:
UPDATE
请注意,PostgreSQL允许您为同一事件定义多个触发器。 (它们在alphabetical order中执行。)在这种情况下,我宁愿继续将触发器分开。由于这两个触发器不可能影响给定行上的相同数据,因此这将更容易维护。它减少了代码量,这意味着对CREATE TRIGGER animal_insert_last_updated_cols
BEFORE INSERT ON animal
FOR EACH ROW
EXECUTE PROCEDURE populate_last_updated_columns();
函数的更改将以最小的麻烦更新所有触发器。我不会担心多个触发器的性能,除非你期望每秒几十populate_last_updated_columns
秒。如果是,那么就进行基准测试,看看性能影响是否对您有影响。