Триггер | как удалить строку вместо обновления на основе значения ячейки - PullRequest
1 голос
/ 15 мая 2019

Postgresql 10/11.
Мне нужно удалить строку вместо обновления в случае, если значение целевой ячейки равно null.

Итак, я создал эту функцию триггера:

CREATE OR REPLACE FUNCTION delete_on_update_related_table() RETURNS trigger
AS $$
    DECLARE
        refColumnName text = TG_ARGV[0];
    BEGIN
        IF TG_NARGS <> 1 THEN
            RAISE EXCEPTION 'Trigger function expects 1 parameters, but got %', TG_NARGS;
        END IF;
        EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
        USING refColumnName, OLD.id;
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

И триггер BEFORE UPDATE:

CREATE TRIGGER proper_delete
BEFORE UPDATE OF def_id
ON public.definition_products 
FOR EACH ROW
WHEN (NEW.def_id IS NULL)
EXECUTE PROCEDURE delete_on_update_related_table('def_id');

Таблица проста:

id  uuid primary key
def_id  uuid not null

Тест:

UPDATE definition_products SET
    def_id = NULL 
WHERE id = 'f47415e8-6b00-4c65-aeb8-cadc15ca5890';
-- rows affected 0

Документация гласит:

Сработали триггеры на уровне строк ДО, может вернуть ноль , чтобы сигнализировать диспетчеру триггеров пропустить остальныеоперация для этой строки (т. е. последующие триггеры не срабатывают, , и INSERT / UPDATE / DELETE не происходит для этой строки ).

Ранее я использовал RULE вместо триггера.Но в том же правиле нельзя использовать предложение WHERE & RETURNING.

Вам необходимо безусловное правило ON UPDATE DO INSTEAD с предложением RETURNING

Итак, есть ли способ?

Ответы [ 2 ]

1 голос
/ 13 июля 2019

Хотя ответ Джереми хороший, все еще есть возможности для улучшения.

Проблемы

Вы должны быть очень точными в определениицели.Ваше заявление:

Мне нужно удалить строку вместо обновления в случае, если значение целевой ячейки равно нулю.

... не означает, что столбец был изменен наNULL в UPDATE под рукой.Может быть, NULL до того, как вы реализовали триггер.Так что не:

<strike>BEFORE UPDATE OF def_id ON public.definition_products</strike>

, а просто:

BEFORE UPDATE ON public.definition_products 

Конечно, если столбец определен NOT NULL (как это, вероятно, и должно быть), эффективной разницы нет - кромедля шума и дополнительной точки отказа. Руководство:

Будет сработать специфичный для столбца триггер (один из которых определен с использованием UPDATE OFcolumn_name)когда любой из его столбцов указан как цель в списке UPDATE команды *1036*.Возможно изменение значения столбца, даже если триггер не запущен, поскольку изменения, внесенные в содержимое строки с помощью триггеров BEFORE UPDATE, не учитываются.

Кроме того, ничто в вашем вопросе не указывает на необходимость динамического SQL.(Это будет в том случае, если вы хотите повторно использовать одну и ту же функцию триггера для нескольких триггеров в разных таблицах. И даже тогда часто лучше просто создать несколько отдельных функций триггера по нескольким причинам: проще, быстрее, меньшеподверженный ошибкам, легче читаемый и поддерживаемый, ...)

Что касается "подверженного ошибкам": ваш исходный динамический оператор был просто недействителен:

<strike>EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
    USING refColumnName, OLD.id;</strike>
  • Can 'Передать имя столбца как значение (refColumnName).
  • Невозможно поместить одинарные кавычки вокруг $2, который передается как значение и, следовательно, не нуждается в кавычках.
  • Неквалифицированный, без кавычек TG_TABLE_NAME может пойти ужаснонеправильный, что особенно важно для функции с большим весом, которая удаляет строки.

Версия Джереми исправляет большинство, но все еще содержит неквалифицированную TG_TABLE_NAME.

Это было бы хорошо:

EXECUTE format('DELETE FROM %s WHERE %I = $1', TG_RELID::regclass, refColumnName)   -- refColumnName still unquoted
USING OLD.id;

Или:

EXECUTE format('DELETE FROM %I.%I WHERE %I = $1', TG_TABLE_SCHEMA, TG_TABLE_NAME, refColumnName)
USING OLD.id;

Связанный:

Решение

Упрощенная функция триггера:

CREATE OR REPLACE FUNCTION delete_on_update_related_table()
  RETURNS trigger AS
$func$
BEGIN
  DELETE FROM public.definition_products WHERE id = OLD.id;  -- def_id?
  RETURN NULL;
END
$func$  LANGUAGE plpgsql;

Более простой триггер:

CREATE TRIGGER proper_delete
BEFORE UPDATE ON public.definition_products 
FOR EACH ROW
WHEN (NEW.def_id IS NULL)                            -- that's the defining condition!
EXECUTE PROCEDURE delete_on_update_related_table();  -- no parameter

Вы, вероятно, хотите использовать OLD.id, а не OLD.def_id.(Строка для удаления лучше всего определяется по PK, а не по столбцу, измененному на NULL.) Но это не совсем понятно.

1 голос
/ 15 мая 2019

Это работает для меня, с небольшими изменениями:

CREATE OR REPLACE FUNCTION delete_on_update_related_table() RETURNS trigger
AS $$
    DECLARE
        refColumnName text = quote_ident(TG_ARGV[0]);
    BEGIN
        IF TG_NARGS <> 1 THEN RAISE EXCEPTION 'Trigger function expects 1 parameters, but got %', TG_NARGS; END IF;
        EXECUTE format('DELETE FROM %s WHERE %s = %s', quote_ident(TG_TABLE_NAME), refColumnName, quote_literal(OLD.id));
        RETURN NULL;
    END;
$$ LANGUAGE plpgsql;

-- create trigger
CREATE TRIGGER proper_delete
BEFORE UPDATE OF def_id
ON public.definition_products 
FOR EACH ROW
WHEN (NEW.def_id IS NULL)
EXECUTE PROCEDURE delete_on_update_related_table('id');   --Note id, not def_id

...