触发|如何删除行而不是根据单元格值进行更新

时间:2019-05-14 21:35:29

标签: postgresql triggers plpgsql

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代替触发器。
但是,无法在同一规则中使用WHERERETURNING子句。

  

您需要一个带RETURNING的无条件ON UPDATE DO INSTEAD规则   条款


所以,没有办法吗?
设计上是错误的,例如c?

2 个答案:

答案 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;
  • 无法将列名作为 value 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