6

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

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

4

2 回答 2

8

您可以尝试删除该行并回滚效果。您不希望在触发器函数中这样做,因为任何异常都会取消对数据库的所有持久更改。手册:

当一个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<>fiddle here
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<>fiddle here
sqlfiddle

在此过程中,我将其设置为完全动态的,包括列的数据类型(当然,它必须与给定的列匹配)。我为此目的使用多态类型。anyelement看:

我还使用format()和 类型的参数regclass来防范 SQLi。看:

于 2013-10-16T00:16:14.060 回答
0

您也可以使用Procedure.

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.
于 2021-12-15T06:01:05.363 回答