?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
PL/pgSQL可以用于定義觸發(fā)器過程。 一個觸發(fā)器過程是用CREATE FUNCTION命令創(chuàng)建的, 創(chuàng)建的形式是一個不接受參數(shù)并且返回trigger類型的函數(shù)。 請注意該函數(shù)即使在CREATE TRIGGER聲明里聲明為準(zhǔn)備接受參數(shù), 它也必需聲明為無參數(shù),因為觸發(fā)器的參數(shù)是通過TG_ARGV傳遞的(下面有描述)。
在一個PL/pgSQL函數(shù)當(dāng)做觸發(fā)器調(diào)用的時候,系統(tǒng)會在頂層的聲明段里自動創(chuàng)建幾個特殊變量。 有如下這些:
數(shù)據(jù)類型是RECORD;該變量為行級觸發(fā)器中的INSERT/UPDATE操作存儲新數(shù)據(jù)行。 在語句級別的觸發(fā)器里以及對INSERT動作,這個變量是NULL。
數(shù)據(jù)類型是RECORD;該變量為行級觸發(fā)器中的UPDATE/DELETE操作存儲舊數(shù)據(jù)行。 在語句級別的觸發(fā)器里以及對INSERT動作,這個變量是NULL。
數(shù)據(jù)類型是name;該變量包含實際觸發(fā)的觸發(fā)器名。
數(shù)據(jù)類型是text;是一個由觸發(fā)器定義決定的字符串(BEFORE或AFTER)。
數(shù)據(jù)類型是text;是一個由觸發(fā)器定義決定的字符串(ROW或STATEMENT)。
數(shù)據(jù)類型是text;是一個說明激活觸發(fā)器的操作的字符串(INSERT, UPDATE,DELETE,或TRUNCATE)。
數(shù)據(jù)類型是oid;是激活觸發(fā)器調(diào)用的表的對象標(biāo)識(OID)。
數(shù)據(jù)類型是name;是激活觸發(fā)器調(diào)用的表的名稱。 反對使用,并會在將來的版本中消失,推薦使用TG_TABLE_NAME。
數(shù)據(jù)類型是name;是激活觸發(fā)器調(diào)用的表的名稱。
數(shù)據(jù)類型是name;是激活觸發(fā)器調(diào)用的表的模式。
數(shù)據(jù)類型是integer;是在CREATE TRIGGER語句里面賦予觸發(fā)器過程的參數(shù)的個數(shù)。
數(shù)據(jù)類型是text的數(shù)組;是CREATE TRIGGER語句里的參數(shù)。 下標(biāo)從 0 開始記數(shù)。非法下標(biāo)(小于 0 或者大于等于tg_nargs)導(dǎo)致返回一個 NULL 值。
一個觸發(fā)器函數(shù)必須返回NULL或者是一個與激活觸發(fā)器運行的表的記錄/行結(jié)構(gòu)完全相同的數(shù)據(jù)。
因BEFORE觸發(fā)的行級別觸發(fā)器可以返回一個 NULL , 告訴觸發(fā)器管理器忽略對該行剩下的操作,也就是說,隨后的觸發(fā)器將不再執(zhí)行, 并且不會對該行產(chǎn)生INSERT/UPDATE/DELETE動作)。 如果返回了一個非 NULL 的行,那么將繼續(xù)對該行數(shù)值進(jìn)行處理。 請注意,返回一個和原來的NEW不同的行數(shù)值將修改那個將插入或更新的行(不過在DELETE的情況下無效)。 因此,如果想在沒有修改行值的同時成功的執(zhí)行觸發(fā)器動作,那么需要返回NEW(或等價的)。 可以用一個值直接代替NEW里的某個數(shù)值并且返回之,或者也可以構(gòu)建一個全新的記錄/行再返回。 In the case of a before-trigger on DELETE, the returned value has no direct effect, but it has to be nonnull to allow the trigger action to proceed. Note that NEW is null in DELETE triggers, so returning that is usually not sensible. A useful idiom in DELETE triggers might be to return OLD.
BEFORE或AFTER語句級別的觸發(fā)器,或者一個AFTER行級別的觸發(fā)器的返回值將總是被忽略; 它們也可以返回 NULL 來忽略返回值。 不過,任何這種類型的觸發(fā)器仍然可以通過拋出一個錯誤來退出整個觸發(fā)器操作。
Example 39-3顯示了一個PL/pgSQL寫的觸發(fā)器過程的例子
Example 39-3. PL/pgSQL觸發(fā)器過程
下面的示例觸發(fā)器的作用是:任何時候表中插入或更新了行,當(dāng)前的用戶名和時間都記錄入行中。 并且它保證給出了雇員名稱并且薪水是一個正數(shù)。
CREATE TABLE emp ( empname text, salary integer, last_date timestamp, last_user text ); CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$ BEGIN -- 檢查是否給出了 empname 和 salary IF NEW.empname IS NULL THEN RAISE EXCEPTION 'empname cannot be null'; END IF; IF NEW.salary IS NULL THEN RAISE EXCEPTION '% cannot have null salary', NEW.empname; END IF; -- 必須付帳給誰? IF NEW.salary < 0 THEN RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; END IF; -- 記住何時何人的薪水被修改了 NEW.last_date := current_timestamp; NEW.last_user := current_user; RETURN NEW; END; $emp_stamp$ LANGUAGE plpgsql; CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
另外一個向表里記錄變化的方法涉及創(chuàng)建一個新表, 然后為后來發(fā)生的每次插入、更新或者刪除動作保存一行。 這個方法可以當(dāng)作對一個表的審計。 Example 39-4顯示了一個PL/pgSQL寫的審計觸發(fā)器過程的例子。
Example 39-4. 一個用于審計的PL/pgSQL觸發(fā)器過程
這個例子觸發(fā)器保證了在emp表上的任何插入、更新、刪除動作都被記錄到了emp_audit表里(也就是審計)。 當(dāng)前時間和用戶名會被記錄到數(shù)據(jù)行里,以及還有執(zhí)行的操作。
CREATE TABLE emp ( empname text NOT NULL, salary integer ); CREATE TABLE emp_audit( operation char(1) NOT NULL, stamp timestamp NOT NULL, userid text NOT NULL, empname text NOT NULL, salary integer ); CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$ BEGIN -- -- 在 emp_audit 里創(chuàng)建一行,反映對 emp 的操作, -- 使用特殊變量 TG_OP 獲取操作類型。 -- IF (TG_OP = 'DELETE') THEN INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*; RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*; RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*; RETURN NEW; END IF; RETURN NULL; -- result is ignored since this is an AFTER trigger END; $emp_audit$ LANGUAGE plpgsql; CREATE TRIGGER emp_audit AFTER INSERT OR UPDATE OR DELETE ON emp FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
觸發(fā)器的一個用途是維持另外一個表的概要。 生成的概要可以用于在某些查詢中代替原始表(通??梢源蟠罂s小運行時間)。 這個技巧經(jīng)常用于數(shù)據(jù)倉庫,這個時候,需要測量的表(叫事實表)可能會非常巨大。 Example 39-5演示了一個PL/pgSQL觸發(fā)器過程的例子, 它為某個數(shù)據(jù)倉庫的一個事實表維護(hù)一個概要表。
Example 39-5. 一個維護(hù)概要表的PL/pgSQL觸發(fā)器過程
下面的模式有一部分是基于The Data Warehouse Toolkit里面的Grocery Store例子。
-- --主表-時間維以及銷售事實。 -- CREATE TABLE time_dimension ( time_key integer NOT NULL, day_of_week integer NOT NULL, day_of_month integer NOT NULL, month integer NOT NULL, quarter integer NOT NULL, year integer NOT NULL ); CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key); CREATE TABLE sales_fact ( time_key integer NOT NULL, product_key integer NOT NULL, store_key integer NOT NULL, amount_sold numeric(12,2) NOT NULL, units_sold integer NOT NULL, amount_cost numeric(12,2) NOT NULL ); CREATE INDEX sales_fact_time ON sales_fact(time_key); -- --摘要表-根據(jù)時間的銷售。 -- CREATE TABLE sales_summary_bytime ( time_key integer NOT NULL, amount_sold numeric(15,2) NOT NULL, units_sold numeric(12) NOT NULL, amount_cost numeric(15,2) NOT NULL ); CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key); -- -- 在 UPDATE, INSERT, DELETE 的時候根新概要字段的函數(shù)和觸發(fā)器 -- CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER AS $maint_sales_summary_bytime$ DECLARE delta_time_key integer; delta_amount_sold numeric(15,2); delta_units_sold numeric(12); delta_amount_cost numeric(15,2); BEGIN -- 計算增/減量 IF (TG_OP = 'DELETE') THEN delta_time_key = OLD.time_key; delta_amount_sold = -1 * OLD.amount_sold; delta_units_sold = -1 * OLD.units_sold; delta_amount_cost = -1 * OLD.amount_cost; ELSIF (TG_OP = 'UPDATE') THEN -- 禁止改變 time_key 的更新 -- (可能并不是很強(qiáng)制,因為 DELETE + INSERT 是大多數(shù)可能產(chǎn)生的修改). 。 IF ( OLD.time_key != NEW.time_key) THEN RAISE EXCEPTION 'Update of time_key : % -> % not allowed', OLD.time_key, NEW.time_key; END IF; delta_time_key = OLD.time_key; delta_amount_sold = NEW.amount_sold - OLD.amount_sold; delta_units_sold = NEW.units_sold - OLD.units_sold; delta_amount_cost = NEW.amount_cost - OLD.amount_cost; ELSIF (TG_OP = 'INSERT') THEN delta_time_key = NEW.time_key; delta_amount_sold = NEW.amount_sold; delta_units_sold = NEW.units_sold; delta_amount_cost = NEW.amount_cost; END IF; --用新數(shù)值插入或更新概要行。 <<insert_update>> LOOP UPDATE sales_summary_bytime SET amount_sold = amount_sold + delta_amount_sold, units_sold = units_sold + delta_units_sold, amount_cost = amount_cost + delta_amount_cost WHERE time_key = delta_time_key; EXIT insert_update WHEN found; BEGIN INSERT INTO sales_summary_bytime ( time_key, amount_sold, units_sold, amount_cost) VALUES ( delta_time_key, delta_amount_sold, delta_units_sold, delta_amount_cost ); EXIT insert_update; EXCEPTION WHEN UNIQUE_VIOLATION THEN -- do nothing END; END LOOP insert_update; RETURN NULL; END; $maint_sales_summary_bytime$ LANGUAGE plpgsql; CREATE TRIGGER maint_sales_summary_bytime AFTER INSERT OR UPDATE OR DELETE ON sales_fact FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime(); INSERT INTO sales_fact VALUES(1,1,1,10,3,15); INSERT INTO sales_fact VALUES(1,2,1,20,5,35); INSERT INTO sales_fact VALUES(2,2,1,40,15,135); INSERT INTO sales_fact VALUES(2,3,1,10,1,13); SELECT * FROM sales_summary_bytime; DELETE FROM sales_fact WHERE product_key = 1; SELECT * FROM sales_summary_bytime; UPDATE sales_fact SET units_sold = units_sold * 2; SELECT * FROM sales_summary_bytime;