【问题标题】:Trigger | how to delete row instead of update based on cell value触发器 |如何根据单元格值删除行而不是更新
【发布时间】:2019-05-14 21:35:28
【问题描述】:

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

文档说:

可以返回 null 之前触发的行级触发器向触发器发出信号 经理跳过该行的其余操作(即, 后续触发器不会被触发,并且 INSERT/UPDATE/DELETE 会触发 此行不会出现)。

以前,我使用RULE 代替触发器。但是没有办法在同一规则中使用WHERE & RETURNING 子句。

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

那么,有什么办法吗?

【问题讨论】:

  • 查询真的应该是 DELETE FROM definition_products where def_id = OLD.id 你有它吗?不应该是 id = OLD.id 吗?

标签: postgresql triggers plpgsql


【解决方案1】:

虽然Jeremy's answer 不错,但仍有改进空间。

问题

您需要非常准确定义目标。您的声明:

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

... 并不意味着在手头的UPDATE 中该列已更改为NULL。之前可能是NULL,比如在您实现触发器之前。所以不是:

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

只是:

BEFORE UPDATE ON public.definition_products 

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

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

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

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

EXECUTE 'DELETE FROM ' || TG_TABLE_NAME || ' WHERE $1 = ''$2'''
    USING refColumnName, OLD.id;
  • 无法将列名作为 (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;

或者:

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 的列来定义。)但这并不完全清楚。

【讨论】:

  • 谢谢,使用 TG_RELID::regclass 确实好很多。
【解决方案2】:

这对我有用,但有一些小改动:

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

【讨论】:

  • 小心TG_TABLE_NAME 并且没有架构限定。
猜你喜欢
  • 1970-01-01
  • 2015-03-03
  • 2017-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多