Postgresql
分配具有動態列名的列
我獲得了要在 ( ) 觸發器中設置的列的名稱
BEFORE UPDATE
,我想將其設置為該OLD
值並忽略任何傳入的內容。我嘗試了以下方法:CREATE OR REPLACE FUNCTION prevent_column_update() RETURNS TRIGGER AS $$ DECLARE col TEXT := TG_ARGV[0]; BEGIN EXECUTE format('SELECT ($1).%I INTO ($2).%I', col, col) USING OLD, NEW; RETURN NEW; END; $$ LANGUAGE plpgsql;
並且可以像這樣使用:
CREATE TRIGGER request_protect_date_price_value BEFORE UPDATE OF date_price ON requests FOR EACH ROW EXECUTE PROCEDURE prevent_column_update('date_price');
但是當更新失敗時:
ERROR: syntax error at or near "(" LINE 1: SELECT ($1).date_price INTO ($2).date_price ^ QUERY: SELECT ($1).date_price INTO ($2).date_price
錯誤是該
INTO
子句不是SQL 命令的一部分。它是 plpgsql 命令的一部分EXECUTE
。動態欄位名稱目前是不可能的,無論是在 SQL 還是 PL/pgSQL 中。但是有一些方法可以繞過這個限制:
概念證明
您可以(ab)使用內置JSON 函式
json_populate_record()
來實現類似的技巧,但目前沒有記錄,並且可能在 Postgres 的未來版本中被刪除。可靠的方法是使用附加hstore模組的文件化**
#=
操作符。**每個數據庫安裝一次模組CREATE EXTENSION IF NOT EXISTS hstore;
然後:
CREATE OR REPLACE FUNCTION prevent_column_update() RETURNS TRIGGER AS $func$ DECLARE _col text := quote_ident(TG_ARGV[0]); _old_val text; _new_val text; BEGIN EXECUTE format('SELECT $1.%1$I, $2.%1$I', _col) INTO _old_val, _new_val -- part of plpgsql command USING OLD, NEW; IF _old_val IS DISTINCT FROM _new_val THEN -- only if it actually changed NEW := NEW #= hstore(_col, _old_val); -- hstore operator #= END IF; RETURN NEW; END $func$ LANGUAGE plpgsql;
請注意,
hstore
使用文本字元串進行操作。其他數據類型的值被強制轉換text
,這適用於我能想到的任何數據類型。但它可能會導致某些類型出現問題(例如浮點數的捨入錯誤)。而這個觸發器定義使案例完整:
CREATE TRIGGER tbl_upbef_nope BEFORE UPDATE ON tbl -- your table here FOR EACH ROW EXECUTE PROCEDURE prevent_column_update('date_price');
在此範例中,列名區分大小寫,因為它作為字元串而不是標識符傳遞。
我會做什麼
只需為每個表編寫一個沒有動態 SQL 的新普通觸發器函式。更少的麻煩,更好的性能。關於程式碼重複的要點:
CREATE OR REPLACE FUNCTION prevent_column_update() RETURNS TRIGGER AS $func$ BEGIN NEW.date_price:= OLD.date_price; -- unconditionally RETURN NEW; END $func$ LANGUAGE plpgsql;
扳機:
CREATE TRIGGER tbl_upbef_nope BEFORE UPDATE OF date_price ON tbl -- your column and table here FOR EACH ROW EXECUTE PROCEDURE prevent_column_update(); -- no param
我將檢查移至觸發器本身,因此除非更新列,否則該函式甚至不會執行。請注意,這可以通過同一張表上的其他觸發器來規避,因為(引用手冊):
僅當列出的列中的至少一個列被提及為命令的目標時,觸發器才會觸發
UPDATE
。因此,如果您無法排除此類額外觸發器,
UPDATE
請無條件觸發觸發器並檢查觸發器函式中的更改。