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

时间:2013-10-15 20:47:44

标签: sql postgresql database-design plpgsql sql-delete

假设我们有一个像这样的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,但我想知道是否有更简单的方法。

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

1 个答案:

答案 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,表示该行可以删除/不可删除/不存在。

-> SQLfiddle demo

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

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;

-> SQLfiddle demo

在使用它时,我使它完全动态,包括列的数据类型(当然,它必须匹配给定的列)。我正在使用polymorphic type anyelement来达到这个目的。在这个相关答案中有更多解释:
How to write a function that returns text or integer values?

我还使用format()regclass类型的参数来防范SQLi。 this related answer on dba.SE中的详细说明。