2015-04-13 2 views
0

Я использую postgres 9.4; У меня есть таблица с уникальным индексом. Я хотел бы изменить имя, добавив суффикс, чтобы гарантировать уникальность имени.postgresql trigger, чтобы сделать имя уникальным

Я создал триггер «до», который вычисляет суффикс. Он хорошо работает в автоматическом режиме. Однако, если два объекта с одинаковым именем вставляются в одну транзакцию, они получают одинаковый уникальный суффикс.

Каков наилучший способ выполнить мою задачу? Есть ли способ справиться с ним с помощью триггера, или я должен ... hmm ... обернуть вставку или обновление в точку сохранения, а затем обработать ошибку?

UPDATE (вос комментарий от @Haleemur Али):

Я не думаю, что мой вопрос зависит от деталей. Существенным моментом является то, что я запрашиваю подмножество коллекции, над которой я хочу применить уникальность, и выберем новое имя ... однако, казалось бы, когда запросы выполняются на двух объектах, одинаково названных в одной транзакции, никто не видит изменения других для нового значения.

Но ... на всякий случай ... мой триггер содержит ("тип" фиксирован параметр функции запуска):

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; 
$$; 
+0

пример кода вашей попытки, пожалуйста –

ответ

1

A BEFORE триггер видит строки, измененные оператором, который в настоящее время запущен. Так что это должно сработать. См. Демонстрацию ниже.

Однако ваш дизайн будет не работать при наличии параллелизма. У вас должно быть LOCK TABLE ... IN EXCLUSIVE MODE таблица, которую вы обновляете, в противном случае одновременных транзакций может получить тот же суффикс. Или, при наличии ограничения UNIQUE, все, кроме одного, будут выходить из строя.

Лично я предлагаю:

  • Создать тумбочка с базовыми именами и счетчик
  • При создании записи, блокировка тумбочка в режиме EXCLUSIVE. Это приведет к сериализации всех сеансов, которые создают записи, которые необходимы, чтобы вы могли:
  • UPDATE side_table SET counter = counter + 1 WHERE name = $1 RETURNING counter, чтобы получить следующий бесплатный идентификатор. Если вы получаете нулевые строки, то вместо этого:
  • Создайте новую запись в боковой таблице, если созданное базовое имя и счетчик установлены на ноль.

Demo показывает, что 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=> 
+0

Спасибо Craig! Является ли преимущество боковой таблицы (просто закрывающей «целевую» таблицу) только для обеспечения более параллельных операций, если они не меняют имя? (И --ah - минимизация синтаксического анализа имен.) Мне также было интересно узнать, могу ли я лучше использовать уникальный индекс для оптимизации, только делая что-то, если у меня есть исключение. Однако руководство говорит, что использование «исключения» в plpsql медленное - это безусловная проверка на боковой таблице быстрее? – shaunc

+0

@shaunc. Вы можете захватить исключение и создать только новое значение, но вы можете столкнуться с взаимоблокировками из-за проблем с порядком блокировки. Я не могу предложить вам гораздо больше советов, не зная много * много о том, что вы пытаетесь сделать, о рабочей нагрузке и т. Д. –

+0

спасибо - я пойду с помощью метода, который вы предложили в качестве базы, затем экспериментируйте. – shaunc

Смежные вопросы