触发器 |如何删除行而不是根据单元格值更新
Trigger | how to delete row instead of update based on cell value
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 = '''''
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');
Table 很简单:
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
文档说:
Row-level triggers fired BEFORE can return null to signal the trigger
manager to skip the rest of the operation for this row (i.e.,
subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does
not occur for this row).
以前,我使用 RULE
而不是触发器。但是无法在同一规则中使用 WHERE
& RETURNING
子句。
You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause
那么,有什么办法吗?
这对我有用,但有一些小改动:
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
虽然很好,但仍有改进的空间。
问题
你需要在 objective 的定义中非常准确。您的陈述:
I need to delete row instead of update in case if target cell value is null.
... 并不意味着手头的 UPDATE
中的列已更改为 NULL
。之前可能是 NULL
,比如在您实施触发器之前。所以不是:
<strike>BEFORE UPDATE OF def_id ON public.definition_products</strike>
但只是:
BEFORE UPDATE ON public.definition_products
当然,如果列被定义为 NOT NULL
(可能应该如此),则没有有效的区别 - 除了噪音和额外的故障点。 The manual:
A column-specific trigger (one defined using the UPDATE OF
column_name
syntax) will fire when any of its columns are listed as targets in the UPDATE
command's SET
list. It is possible for a column's value to change even when the trigger is not fired, because changes made to the row's contents by BEFORE UPDATE
triggers are not considered.
此外,您的问题中没有任何内容表明需要动态 SQL。 (如果您想为不同表上的多个触发器重用相同的触发器函数,那么 会 就是这种情况。即便如此,出于多种原因,只创建几个不同的触发器函数通常会更好:更简单,更快,更少 error-prone,更易于阅读和维护,...)
至于“error-prone”:你原来的动态语句是无效的:
<strike>EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE = '''''
USING refColumnName, OLD.id;</strike>
- 无法将列名称作为 值 (
refColumnName
) 传递。
- 不能在
</code> 周围加上单引号,它作为 <em>value</em> 传递,因此不需要引号。</li>
<li>不合格、未加引号的 <code>TG_TABLE_NAME
可能会出错,这对于删除行的 heavy-weight 函数尤其重要。
Jeremy 的版本修复了大部分内容,但仍然具有不合格的 TG_TABLE_NAME
。
这样就好了:
EXECUTE format('DELETE FROM %s WHERE %I = ', TG_RELID::regclass, refColumnName) -- refColumnName still unquoted
USING OLD.id;
或者:
EXECUTE format('DELETE FROM %I.%I WHERE %I = ', TG_TABLE_SCHEMA, TG_TABLE_NAME, refColumnName)
USING OLD.id;
相关:
- Table name as a PostgreSQL function parameter
解决方案
更简单的触发函数:
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
的列来定义。)但这并不完全清楚。
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 = '''''
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');
Table 很简单:
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
文档说:
Row-level triggers fired BEFORE can return null to signal the trigger manager to skip the rest of the operation for this row (i.e., subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does not occur for this row).
以前,我使用 RULE
而不是触发器。但是无法在同一规则中使用 WHERE
& RETURNING
子句。
You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause
那么,有什么办法吗?
这对我有用,但有一些小改动:
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
虽然
问题
你需要在 objective 的定义中非常准确。您的陈述:
I need to delete row instead of update in case if target cell value is null.
... 并不意味着手头的 UPDATE
中的列已更改为 NULL
。之前可能是 NULL
,比如在您实施触发器之前。所以不是:
<strike>BEFORE UPDATE OF def_id ON public.definition_products</strike>
但只是:
BEFORE UPDATE ON public.definition_products
当然,如果列被定义为 NOT NULL
(可能应该如此),则没有有效的区别 - 除了噪音和额外的故障点。 The manual:
A column-specific trigger (one defined using the
UPDATE OF
column_name
syntax) will fire when any of its columns are listed as targets in theUPDATE
command'sSET
list. It is possible for a column's value to change even when the trigger is not fired, because changes made to the row's contents byBEFORE UPDATE
triggers are not considered.
此外,您的问题中没有任何内容表明需要动态 SQL。 (如果您想为不同表上的多个触发器重用相同的触发器函数,那么 会 就是这种情况。即便如此,出于多种原因,只创建几个不同的触发器函数通常会更好:更简单,更快,更少 error-prone,更易于阅读和维护,...)
至于“error-prone”:你原来的动态语句是无效的:
<strike>EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE = '''''
USING refColumnName, OLD.id;</strike>
- 无法将列名称作为 值 (
refColumnName
) 传递。 - 不能在
</code> 周围加上单引号,它作为 <em>value</em> 传递,因此不需要引号。</li> <li>不合格、未加引号的 <code>TG_TABLE_NAME
可能会出错,这对于删除行的 heavy-weight 函数尤其重要。
Jeremy 的版本修复了大部分内容,但仍然具有不合格的 TG_TABLE_NAME
。
这样就好了:
EXECUTE format('DELETE FROM %s WHERE %I = ', TG_RELID::regclass, refColumnName) -- refColumnName still unquoted
USING OLD.id;
或者:
EXECUTE format('DELETE FROM %I.%I WHERE %I = ', TG_TABLE_SCHEMA, TG_TABLE_NAME, refColumnName)
USING OLD.id;
相关:
- Table name as a PostgreSQL function parameter
解决方案
更简单的触发函数:
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
的列来定义。)但这并不完全清楚。