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

Geo*_*rge 4 postgresql triggers plpgsql

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;
Run Code Online (Sandbox Code Playgroud)

还有一个BEFORE UPDATE触发器:

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;
Run Code Online (Sandbox Code Playgroud)

表格很简单:

id  uuid primary key
def_id  uuid not null
Run Code Online (Sandbox Code Playgroud)

测试:

UPDATE definition_products SET
    def_id = NULL 
WHERE id = 'f47415e8-6b00-4c65-aeb8-cadc15ca5890';
-- rows affected 0
Run Code Online (Sandbox Code Playgroud)

文档说:

BEFORE 触发的行级触发器可以返回 null以通知触发器管理器跳过该行的其余操作(即,不会触发后续触发器,并且不会对该行发生 INSERT/UPDATE/DELETE)。

以前,我使用 aRULE代替触发器。但无法在同一规则中使用WHERE&子句。RETURNING

您需要带有 RETURNING 子句的无条件 ON UPDATE DO INSTEAD 规则

那么,有办法吗?

Erw*_*ter 5

虽然杰里米的回答很好,但仍有改进的空间。

问题

您需要非常准确地定义目标。您的陈述:

如果目标单元格值为空,我需要删除行而不是更新。

...并不意味着该列已更改为NULL当前UPDATE状态。可能是NULL在你实施触发器之前。所以不是:

BEFORE UPDATE OF def_id ON public.definition_products
Run Code Online (Sandbox Code Playgroud)

只是:

BEFORE UPDATE ON public.definition_products 
Run Code Online (Sandbox Code Playgroud)

当然,如果定义了列NOT NULL(可能应该如此),则没有任何有效的区别 - 除了噪音和额外的故障点。手册:

当特定列的任何列被列为命令列表UPDATE OFcolumn_name中的目标时,将触发特定于列的触发器(使用语法定义的触发器) 。即使未触发触发器,列的值也可能发生更改,因为不考虑触发器对行内容所做的更改。UPDATESETBEFORE UPDATE

另外,您的问题中没有任何内容表明需要动态 SQL。( 如果您想为不同表上的多个触发器重用相同的触发器函数,情况就会如此。即使如此,出于多种原因,最好只创建几个不同的触发器函数:更简单、更快、更不容易出错、更容易阅读和维护,...)

至于“容易出错”:你原来的动态语句是无效的:

EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
    USING refColumnName, OLD.id;
Run Code Online (Sandbox Code Playgroud)
  • 无法将列名作为( refColumnName) 传递。
  • 不能在 周围放置单引号$2,它作为传递,因此不需要引号。
  • 不合格、不带引号的函数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;
Run Code Online (Sandbox Code Playgroud)

或者:

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

有关的:

解决方案

更简单的触发功能:

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;
Run Code Online (Sandbox Code Playgroud)

更简单的触发:

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
Run Code Online (Sandbox Code Playgroud)

您可能想使用OLD.id,但不是OLD.def_id。(要删除的行最好由它的 PK 来定义,而不是由更改为 的列来定义NULL。)但这并不完全清楚。