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=>