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
);

マスター行が実際に削除しようとせずに削除可能かどうかを (トリガー関数内から) チェックする方法はありますか? 明らかな方法は、すべての参照テーブルに対して 1 つずつ 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
古い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
古いsqlfiddle

その間、列のデータ型を含めて完全に動的にしました(もちろん、指定された列と一致する必要があります)。そのためにポリモーフィック型anyelementを使用しています。見る:

SQLi から保護するためformat()に、型のパラメーターも使用します。regclass見る:

于 2013-10-16T00:16:14.060 に答える