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

Nik*_*dun 5 sql postgresql database-design plpgsql sql-delete

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

CREATE TABLE master (
    id INT PRIMARY KEY,
    ...
);
Run Code Online (Sandbox Code Playgroud)

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

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

有没有办法检查(从触发器功能内)主行是否可删除而不实际尝试删除它?显而易见的方法是逐个对所有引用表执行SELECT,但我想知道是否有更简单的方法.

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

Erw*_*ter 7

您可以尝试删除行并回滚效果.您不希望在触发器函数中执行此操作,因为任何异常都会取消对数据库的所有持久更改.请考虑手册中的引用:

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

返回TRUE/ FALSE/ NULL表示该行可以删除/不可删除/不存在.

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

- > SQLfiddle演示

在它的同时,我使它完全动态,包括列的数据类型(当然,它必须匹配给定的列).我正在为此目的使用多态类型anyelement.在这个相关答案中有更多解释:
如何编写一个返回文本或整数值的函数?

我还使用format()和类型参数regclass来防范SQLi.关于dba.SE的相关答案的详细解释.