如何以编程方式检查行是否可删除?

问题描述 投票:0回答:3

假设我们有一个像这样的 PostgreSQL 表:

CREATE TABLE master (
    id INT PRIMARY KEY,
    ...
);

以及许多其他使用外键引用它的表:

CREATE TABLE other (
    id INT PRIMARY KEY,
    id_master INT NOT NULL,
    ...
    CONSTRAINT other_id_master_fkey FOREIGN KEY (id_master)
                                    REFERENCES master (id) ON DELETE RESTRICT
);

有没有办法(从触发器函数内)检查主行是否可删除,而不实际尝试删除它?最明显的方法是对所有引用表一一进行 SELECT,但我想知道是否有更简单的方法。

我需要这个的原因是,我有一个包含分层数据的表,其中任何行都可以有子行,并且只有层次结构最低的子行可以被其他表引用。因此,当一行即将成为父行时,我需要检查它是否已在任何地方被引用。如果是,则它不能成为父行,并且拒绝插入新的子行。

sql postgresql database-design plpgsql sql-delete
3个回答
9
投票
说明书:

EXCEPTION

子句捕获错误时,局部变量 PL/pgSQL 函数保持错误发生时的状态,

但是
块内对持久数据库状态的所有更改都会回滚

粗体

强调我的。 但是您可以将其包装到单独的块或单独的

plpgsql 函数

中,并在那里捕获异常以防止对主(触发)函数产生影响。 CREATE OR REPLACE FUNCTION f_can_del(_id int) RETURNS boolean AS $func$ BEGIN DELETE FROM master WHERE master_id = _id; -- DELETE is always rolled back IF NOT FOUND THEN RETURN NULL; -- ID not found, return NULL END IF; RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception EXCEPTION WHEN FOREIGN_KEY_VIOLATION THEN RETURN FALSE; WHEN SQLSTATE 'MYERR' THEN RETURN TRUE; -- other exceptions are propagated as usual END $func$ LANGUAGE plpgsql;

这将返回 
TRUE

/

FALSE
/
NULL
表示该行可以删除/不可删除/不存在。

db<>小提琴

这里
sqlfiddle 可以轻松地使此函数动态化以测试任何表/列/值。

PostgreSQL 9.2

起,您还可以报告哪个表被阻塞。 PostgreSQL 9.3
或更高版本提供了更详细的信息。 任意表、列和类型的通用函数

为什么您在评论中发布的

对动态函数的尝试

失败了? 手册中的这句话应该给出一个线索:

特别注意,
EXECUTE

更改了

GET DIAGNOSTICS
的输出,但不更改
FOUND

它适用于

GET DIAGNOSTICS

CREATE OR REPLACE FUNCTION f_can_del(_tbl regclass, _col text, _id int) RETURNS boolean AS $func$ DECLARE _ct int; -- to receive count of deleted rows BEGIN EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col) USING _id; -- exception if other rows depend GET DIAGNOSTICS _ct = ROW_COUNT; IF _ct > 0 THEN RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception ELSE RETURN NULL; -- ID not found, return NULL END IF; EXCEPTION WHEN FOREIGN_KEY_VIOLATION THEN RETURN FALSE; WHEN SQLSTATE 'MYERR' THEN RETURN TRUE; -- other exceptions are propagated as usual END $func$ LANGUAGE plpgsql;

db<>小提琴

这里
sqlfiddle 在这个过程中,我将其设置为“完全动态”,包括列的数据类型(当然,它必须与给定的列匹配)。为此,我使用

多态类型

anyelement。参见:

如何编写返回文本或整数值的函数?

regclass

类型的参数来防范 SQLi。参见:


Postgres 函数中的 SQL 注入与准备好的查询

  • 您也可以使用
Procedure

0
投票

CREATE OR REPLACE procedure p_delable(_tbl text, _col text, _id int) AS $$ DECLARE _ct bigint; _exists boolean; -- to receive count of deleted rows BEGIN _exists := (SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = $1 )); IF _exists THEN EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col) USING _id; -- exception if other rows depend GET DIAGNOSTICS _ct = ROW_COUNT; IF _ct > 0 THEN RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception ELSE RAISE NOTICE 'no records found. no records will be deleted'; END IF; ELSE raise notice 'Input text is invalid table name.'; END IF; EXCEPTION WHEN undefined_column then raise notice 'Input text is invalid column name.'; WHEN undefined_table then raise notice 'Input text is invalid table name.'; WHEN FOREIGN_KEY_VIOLATION THEN RAISE NOTICE 'foreign key violation, cannot be deleted.'; WHEN SQLSTATE 'MYERR' THEN RAISE NOTICE 'rows % found and can be deleted.', _ct; END $$ LANGUAGE plpgsql;

你可以调用它,也可以验证你的输入。

call p_delable('parent_tree', 'parent_id',30);

将会得到:

NOTICE:  no records found. no records will be deleted

让我们尝试一个实际存在的行。

call p_delable('parent_tree', 'parent_id',3);

它会回来的

NOTICE:  rows 1 found and can be deleted.

它还可以检查您的输入表名称是否存在于

public
模式中。

call p_delable('parent_tre', 'parent_id',3);

它会给你通知:

NOTICE:  Input text is invalid table name.

如果存在外键 ON DELETE RESTRICT 约束,该约束禁止删除被推迟的行(主要是因为该约束是可推迟的并且最初是推迟的),则尝试删除和回滚将不起作用。您没有得到任何信息

0
投票

也许有人必须编写一个功能请求来向 postgresql 添加 SQL 命令,该请求检查实际(子)事务上的 COMMIT 是否不会失败(包括检查重复键违规、约束违规、范围违规以及所有 lametta,但这样做没有真正的承诺)。

© www.soinside.com 2019 - 2024. All rights reserved.