postgresql触发器使名称唯一
postgresql trigger to make name unique
我正在使用 postgres 9.4;我有一个带有唯一索引的 table。我想通过添加后缀来改变名称以确保名称是唯一的。
我创建了一个计算后缀的 "before" 触发器。它在自动提交模式下运行良好。但是,如果在同一事务中插入两个具有相同名称的项目,则它们都将获得相同的唯一后缀。
完成任务的最佳方法是什么?有没有办法用触发器来处理它,或者我应该......嗯......将插入或更新包装在一个保存点中,然后处理错误?
更新(来自@Haleemur Ali 的评论):
我不认为我的问题取决于细节。重点是我查询了我想要强制执行唯一性的集合的子集,并且
选择一个新名称...但是,当查询 运行 在同一事务中同名的两个对象上时,一个对象看不到其他人对新值的修改。
但是...以防万一...我的触发器包含("type" 是触发器函数的固定参数):
select find_unique(coalesce(new.name, capitalize(type)),
'vis_operation', 'name', format(
'sheet_id = %s', new.sheet_id )) into new.name;
其中 "find_unique" 包含:
create or replace function find_unique(
stem text, table_name text, column_name text, where_expr text = null)
returns text language plpgsql as $$
declare
table_nt text = quote_ident(table_name);
column_nt text = quote_ident(column_name);
bstem text = replace(btrim(stem),'''', '''''');
find_re text = quote_literal(format('^%s(( \d+$)|$)', bstem));
xtct_re text = quote_literal(format('^(%s ?)', bstem));
where_ext text = case when where_expr is null then '' else 'and ' || where_expr end;
query_exists text = format(
$Q$ select 1 from %1$s where btrim(%2$s) = %3$s %4$s $Q$,
table_nt, column_nt, quote_literal(bstem), where_ext );
query_max text = format($q$
select max(coalesce(nullif(regexp_replace(%1$s, %4$s, '', 'i'), ''), '0')::int)
from %2$s where %1$s ~* %3$s %5$s
$q$,
column_nt, table_nt, find_re, xtct_re, where_ext );
last int;
i int;
begin
-- if no exact match, use exact
execute query_exists;
get diagnostics i = row_count;
if i = 0 then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
-- find stem w/ number, use max plus one.
execute query_max into last;
if last is null then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
return format('%s %s', bstem, last + 1);
end;
$$;
BEFORE
触发器查看由当前 运行 语句修改的行。所以这应该有效。请参阅下面的演示。
但是,您的设计不会在并发情况下工作。您必须 LOCK TABLE ... IN EXCLUSIVE MODE
您正在更新的 table ,否则 并发事务 可能会得到相同的后缀。或者,在存在 UNIQUE
约束的情况下,除一个之外的所有约束都会出错。
个人建议:
- 用基本名称和计数器
创建边table
- 创建条目时,将 table 侧锁定为
EXCLUSIVE
模式。这将序列化创建条目的所有会话,这是必需的,以便您可以:
UPDATE side_table SET counter = counter + 1 WHERE name = RETURNING counter
获取下一个免费ID。如果得到零行,则改为:
- 如果正在创建基本名称并且计数器设置为零,则在 table 侧创建一个新条目。
演示显示 BEFORE
触发器可以看到在同一语句中插入的行,但不是触发触发器的行:
craig=> CREATE TABLE demo(id integer);
CREATE TABLE
craig=> \e
CREATE FUNCTION
craig=> CREATE OR REPLACE FUNCTION demo_tg() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
row record;
BEGIN
FOR row IN SELECT * FROM demo
LOOP
RAISE NOTICE 'Row is %',row;
END LOOP;
IF tg_op = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$;
CREATE FUNCTION
craig=> CREATE TRIGGER demo_tg BEFORE INSERT OR UPDATE OR DELETE ON demo FOR EACH ROW EXECUTE PROCEDURE demo_tg();
CREATE TRIGGER
craig=> INSERT INTO demo(id) VALUES (1),(2);
NOTICE: Row is (1)
INSERT 0 2
craig=> INSERT INTO demo(id) VALUES (3),(4);
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
INSERT 0 2
craig=> UPDATE demo SET id = id + 100;
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (103)
UPDATE 4
craig=>
我正在使用 postgres 9.4;我有一个带有唯一索引的 table。我想通过添加后缀来改变名称以确保名称是唯一的。
我创建了一个计算后缀的 "before" 触发器。它在自动提交模式下运行良好。但是,如果在同一事务中插入两个具有相同名称的项目,则它们都将获得相同的唯一后缀。
完成任务的最佳方法是什么?有没有办法用触发器来处理它,或者我应该......嗯......将插入或更新包装在一个保存点中,然后处理错误?
更新(来自@Haleemur Ali 的评论):
我不认为我的问题取决于细节。重点是我查询了我想要强制执行唯一性的集合的子集,并且 选择一个新名称...但是,当查询 运行 在同一事务中同名的两个对象上时,一个对象看不到其他人对新值的修改。
但是...以防万一...我的触发器包含("type" 是触发器函数的固定参数):
select find_unique(coalesce(new.name, capitalize(type)),
'vis_operation', 'name', format(
'sheet_id = %s', new.sheet_id )) into new.name;
其中 "find_unique" 包含:
create or replace function find_unique(
stem text, table_name text, column_name text, where_expr text = null)
returns text language plpgsql as $$
declare
table_nt text = quote_ident(table_name);
column_nt text = quote_ident(column_name);
bstem text = replace(btrim(stem),'''', '''''');
find_re text = quote_literal(format('^%s(( \d+$)|$)', bstem));
xtct_re text = quote_literal(format('^(%s ?)', bstem));
where_ext text = case when where_expr is null then '' else 'and ' || where_expr end;
query_exists text = format(
$Q$ select 1 from %1$s where btrim(%2$s) = %3$s %4$s $Q$,
table_nt, column_nt, quote_literal(bstem), where_ext );
query_max text = format($q$
select max(coalesce(nullif(regexp_replace(%1$s, %4$s, '', 'i'), ''), '0')::int)
from %2$s where %1$s ~* %3$s %5$s
$q$,
column_nt, table_nt, find_re, xtct_re, where_ext );
last int;
i int;
begin
-- if no exact match, use exact
execute query_exists;
get diagnostics i = row_count;
if i = 0 then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
-- find stem w/ number, use max plus one.
execute query_max into last;
if last is null then
return coalesce(bstem, capitalize(right(table_nt,4)));
end if;
return format('%s %s', bstem, last + 1);
end;
$$;
BEFORE
触发器查看由当前 运行 语句修改的行。所以这应该有效。请参阅下面的演示。
但是,您的设计不会在并发情况下工作。您必须 LOCK TABLE ... IN EXCLUSIVE MODE
您正在更新的 table ,否则 并发事务 可能会得到相同的后缀。或者,在存在 UNIQUE
约束的情况下,除一个之外的所有约束都会出错。
个人建议:
- 用基本名称和计数器 创建边table
- 创建条目时,将 table 侧锁定为
EXCLUSIVE
模式。这将序列化创建条目的所有会话,这是必需的,以便您可以: UPDATE side_table SET counter = counter + 1 WHERE name = RETURNING counter
获取下一个免费ID。如果得到零行,则改为:- 如果正在创建基本名称并且计数器设置为零,则在 table 侧创建一个新条目。
演示显示 BEFORE
触发器可以看到在同一语句中插入的行,但不是触发触发器的行:
craig=> CREATE TABLE demo(id integer);
CREATE TABLE
craig=> \e
CREATE FUNCTION
craig=> CREATE OR REPLACE FUNCTION demo_tg() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
row record;
BEGIN
FOR row IN SELECT * FROM demo
LOOP
RAISE NOTICE 'Row is %',row;
END LOOP;
IF tg_op = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$;
CREATE FUNCTION
craig=> CREATE TRIGGER demo_tg BEFORE INSERT OR UPDATE OR DELETE ON demo FOR EACH ROW EXECUTE PROCEDURE demo_tg();
CREATE TRIGGER
craig=> INSERT INTO demo(id) VALUES (1),(2);
NOTICE: Row is (1)
INSERT 0 2
craig=> INSERT INTO demo(id) VALUES (3),(4);
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
INSERT 0 2
craig=> UPDATE demo SET id = id + 100;
NOTICE: Row is (1)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (2)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (3)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (4)
NOTICE: Row is (101)
NOTICE: Row is (102)
NOTICE: Row is (103)
UPDATE 4
craig=>