postgresql触发器使名称唯一

时间:2015-04-13 02:19:18

标签: postgresql postgresql-9.4

我正在使用postgres 9.4;我有一个具有唯一索引的表。我想通过添加后缀来改变名称,以确保名称是唯一的。

我创建了一个“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;
$$;

1 个答案:

答案 0 :(得分:1)

BEFORE触发器会看到由当前正在运行的语句修改的行。所以这应该工作。见下面的演示。

但是,您的设计在并发存在的情况下工作。您必须LOCK TABLE ... IN EXCLUSIVE MODE要更新的表,否则并发事务可能会获得相同的后缀。或者,如果存在UNIQUE约束,则除了一个之外的所有约束都会出错。

我个人建议:

  • 使用基本名称和计数器
  • 创建一个旁边表
  • 创建条目时,请以EXCLUSIVE模式锁定边桌。这将序列化所有创建条目的会话,这是必要的,以便您可以:
  • UPDATE side_table SET counter = counter + 1 WHERE name = $1 RETURNING counter获取下一个免费ID。如果你获得零行,那么:
  • 如果正在创建基本名称且计数器设置为零,则在边表中创建一个新条目。

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