PostgreSQL 10/11。
如果目标单元格值为null
,我需要删除行而不是更新。
所以我创建了BEFORE UPDATE
触发器。
-- drop exist trigger
DROP TRIGGER IF EXISTS proper_delete ON public.definition_products;
-- 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('def_id');
和功能:
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;
表很简单:
id:uuid(主键)
def_id:uuid(不为null)
测试:
UPDATE definition_products SET
def_id = NULL
WHERE id = 'f47415e8-6b00-4c65-aeb8-cadc15ca5890';
-- rows affected 0
文档说:
在可以返回null 之前触发行级触发器 经理跳过此行的其余操作(即, 随后的触发器不触发,,而INSERT / UPDATE / DELETE执行 此行不会发生)。
以前,我使用RULE
代替触发器。
但是,无法在同一规则中使用WHERE
和RETURNING
子句。
您需要一个带RETURNING的无条件ON UPDATE DO INSTEAD规则 条款
所以,没有办法吗?
设计上是错误的,例如c?
答案 0 :(得分:1)
Jeremy's answer很好,但仍有改进的空间。
您需要在目标定义中非常准确。您的声明:
如果目标单元格值为空,我需要删除行而不是更新。
...并不意味着该列已在手头NULL
中更改为UPDATE
。可能在实现触发器之前就NULL
了。所以不是:
BEFORE UPDATE OF def_id ON public.definition_products
但是:
BEFORE UPDATE ON public.definition_products
当然,如果定义了列NOT NULL
(可能应该如此),则没有任何有效区别-除了噪音和其他故障点。 The manual:
列特定的触发器(使用
UPDATE OF
column_name
语法定义的触发器)将在其任何列被列为目标时触发在UPDATE
命令的SET
列表中。即使不触发触发器,也可能会更改列的值,因为不会考虑通过BEFORE UPDATE
触发器对行内容所做的更改。
此外,您的问题中没有任何内容表明需要动态SQL。 (如果您想针对不同表上的多个触发器重用相同的触发器函数,那么将会是这样。即使那样,出于多种原因,最好创建几个不同的触发器函数通常也更好:更简单,更快,较少出错,易于阅读和维护,...)
至于“容易出错”:您原来的动态语句无效:
EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
USING refColumnName, OLD.id;
refColumnName
)传递。 $2
周围加上单引号,它以 value 的形式传递,因此不需要引号。TG_TABLE_NAME
可能会出错,这对于删除行的重量级函数尤其重要。杰里米(Jeremy)的版本修复最多,但仍具有不合格的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 :(得分:0)
这对我有用,但有一些小的更改:
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