假设我们有一个像这样的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,但我想知道是否有更简单的方法。
我需要的原因是我有一个包含分层数据的表,其中任何行都可以有子行,并且只有层次结构中最低的子行可以被其他表引用。因此,当一行即将成为父行时,我需要检查它是否已在任何地方引用。如果是,则它不能成为父行,并且拒绝插入新的子行。
答案 0 :(得分:6)
您可以尝试删除行并回滚效果。您不希望在触发器函数中执行此操作,因为任何异常都会取消对数据库的所有持久更改。请考虑手册中的引用:
当
EXCEPTION
子句捕获错误时,局部变量为 PL / pgSQL函数保持与错误发生时一样,但是 对块内持久性数据库状态的所有更改都将回滚。
大胆强调我的。
但是你可以把它包装成一个单独的块或一个单独的 plpgsql函数并在那里捕获异常以防止对main(触发器)函数产生影响。
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
,表示该行可以删除/不可删除/不存在。
可以轻松地使此函数动态化以测试任何表/列/值。
在PostgreSQL 9.2中,您还可以报告哪个表格被阻止 current PostgreSQL 9.3提供了更详细的信息。
为什么您在评论中发布的the attempt on a dynamic function失败了? This quote from the manual应该提供线索:
特别注意
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;
在使用它时,我使它完全动态,包括列的数据类型(当然,它必须匹配给定的列)。我正在使用polymorphic type anyelement
来达到这个目的。在这个相关答案中有更多解释:
How to write a function that returns text or integer values?
我还使用format()
和regclass
类型的参数来防范SQLi。 this related answer on dba.SE中的详细说明。