?
This document uses PHP Chinese website manual Release
PL/pgSQL可以用于定義觸發(fā)器過(guò)程。 一個(gè)觸發(fā)器過(guò)程是用CREATE FUNCTION命令創(chuàng)建的, 創(chuàng)建的形式是一個(gè)不接受參數(shù)并且返回trigger類(lèi)型的函數(shù)。 請(qǐng)注意該函數(shù)即使在CREATE TRIGGER聲明里聲明為準(zhǔn)備接受參數(shù), 它也必需聲明為無(wú)參數(shù),因?yàn)橛|發(fā)器的參數(shù)是通過(guò)TG_ARGV傳遞的(下面有描述)。
在一個(gè)PL/pgSQL函數(shù)當(dāng)做觸發(fā)器調(diào)用的時(shí)候,系統(tǒng)會(huì)在頂層的聲明段里自動(dòng)創(chuàng)建幾個(gè)特殊變量。 有如下這些:
數(shù)據(jù)類(lèi)型是RECORD;該變量為行級(jí)觸發(fā)器中的INSERT/UPDATE操作存儲(chǔ)新數(shù)據(jù)行。 在語(yǔ)句級(jí)別的觸發(fā)器里以及對(duì)INSERT動(dòng)作,這個(gè)變量是NULL。
數(shù)據(jù)類(lèi)型是RECORD;該變量為行級(jí)觸發(fā)器中的UPDATE/DELETE操作存儲(chǔ)舊數(shù)據(jù)行。 在語(yǔ)句級(jí)別的觸發(fā)器里以及對(duì)INSERT動(dòng)作,這個(gè)變量是NULL。
數(shù)據(jù)類(lèi)型是name;該變量包含實(shí)際觸發(fā)的觸發(fā)器名。
數(shù)據(jù)類(lèi)型是text;是一個(gè)由觸發(fā)器定義決定的字符串(BEFORE或AFTER)。
數(shù)據(jù)類(lèi)型是text;是一個(gè)由觸發(fā)器定義決定的字符串(ROW或STATEMENT)。
數(shù)據(jù)類(lèi)型是text;是一個(gè)說(shuō)明激活觸發(fā)器的操作的字符串(INSERT, UPDATE,DELETE,或TRUNCATE)。
數(shù)據(jù)類(lèi)型是oid;是激活觸發(fā)器調(diào)用的表的對(duì)象標(biāo)識(shí)(OID)。
數(shù)據(jù)類(lèi)型是name;是激活觸發(fā)器調(diào)用的表的名稱(chēng)。 反對(duì)使用,并會(huì)在將來(lái)的版本中消失,推薦使用TG_TABLE_NAME。
數(shù)據(jù)類(lèi)型是name;是激活觸發(fā)器調(diào)用的表的名稱(chēng)。
數(shù)據(jù)類(lèi)型是name;是激活觸發(fā)器調(diào)用的表的模式。
數(shù)據(jù)類(lèi)型是integer;是在CREATE TRIGGER語(yǔ)句里面賦予觸發(fā)器過(guò)程的參數(shù)的個(gè)數(shù)。
數(shù)據(jù)類(lèi)型是text的數(shù)組;是CREATE TRIGGER語(yǔ)句里的參數(shù)。 下標(biāo)從 0 開(kāi)始記數(shù)。非法下標(biāo)(小于 0 或者大于等于tg_nargs)導(dǎo)致返回一個(gè) NULL 值。
一個(gè)觸發(fā)器函數(shù)必須返回NULL或者是一個(gè)與激活觸發(fā)器運(yùn)行的表的記錄/行結(jié)構(gòu)完全相同的數(shù)據(jù)。
因BEFORE觸發(fā)的行級(jí)別觸發(fā)器可以返回一個(gè) NULL , 告訴觸發(fā)器管理器忽略對(duì)該行剩下的操作,也就是說(shuō),隨后的觸發(fā)器將不再執(zhí)行, 并且不會(huì)對(duì)該行產(chǎn)生INSERT/UPDATE/DELETE動(dòng)作)。 如果返回了一個(gè)非 NULL 的行,那么將繼續(xù)對(duì)該行數(shù)值進(jìn)行處理。 請(qǐng)注意,返回一個(gè)和原來(lái)的NEW不同的行數(shù)值將修改那個(gè)將插入或更新的行(不過(guò)在DELETE的情況下無(wú)效)。 因此,如果想在沒(méi)有修改行值的同時(shí)成功的執(zhí)行觸發(fā)器動(dòng)作,那么需要返回NEW(或等價(jià)的)。 可以用一個(gè)值直接代替NEW里的某個(gè)數(shù)值并且返回之,或者也可以構(gòu)建一個(gè)全新的記錄/行再返回。 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語(yǔ)句級(jí)別的觸發(fā)器,或者一個(gè)AFTER行級(jí)別的觸發(fā)器的返回值將總是被忽略; 它們也可以返回 NULL 來(lái)忽略返回值。 不過(guò),任何這種類(lèi)型的觸發(fā)器仍然可以通過(guò)拋出一個(gè)錯(cuò)誤來(lái)退出整個(gè)觸發(fā)器操作。
Example 39-3顯示了一個(gè)PL/pgSQL寫(xiě)的觸發(fā)器過(guò)程的例子
Example 39-3. PL/pgSQL觸發(fā)器過(guò)程
下面的示例觸發(fā)器的作用是:任何時(shí)候表中插入或更新了行,當(dāng)前的用戶名和時(shí)間都記錄入行中。 并且它保證給出了雇員名稱(chēng)并且薪水是一個(gè)正數(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; -- 必須付帳給誰(shuí)? IF NEW.salary < 0 THEN RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; END IF; -- 記住何時(shí)何人的薪水被修改了 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();
另外一個(gè)向表里記錄變化的方法涉及創(chuàng)建一個(gè)新表, 然后為后來(lái)發(fā)生的每次插入、更新或者刪除動(dòng)作保存一行。 這個(gè)方法可以當(dāng)作對(duì)一個(gè)表的審計(jì)。 Example 39-4顯示了一個(gè)PL/pgSQL寫(xiě)的審計(jì)觸發(fā)器過(guò)程的例子。
Example 39-4. 一個(gè)用于審計(jì)的PL/pgSQL觸發(fā)器過(guò)程
這個(gè)例子觸發(fā)器保證了在emp表上的任何插入、更新、刪除動(dòng)作都被記錄到了emp_audit表里(也就是審計(jì))。 當(dāng)前時(shí)間和用戶名會(huì)被記錄到數(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)建一行,反映對(duì) emp 的操作, -- 使用特殊變量 TG_OP 獲取操作類(lèi)型。 -- 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ā)器的一個(gè)用途是維持另外一個(gè)表的概要。 生成的概要可以用于在某些查詢中代替原始表(通??梢源蟠罂s小運(yùn)行時(shí)間)。 這個(gè)技巧經(jīng)常用于數(shù)據(jù)倉(cāng)庫(kù),這個(gè)時(shí)候,需要測(cè)量的表(叫事實(shí)表)可能會(huì)非常巨大。 Example 39-5演示了一個(gè)PL/pgSQL觸發(fā)器過(guò)程的例子, 它為某個(gè)數(shù)據(jù)倉(cāng)庫(kù)的一個(gè)事實(shí)表維護(hù)一個(gè)概要表。
Example 39-5. 一個(gè)維護(hù)概要表的PL/pgSQL觸發(fā)器過(guò)程
下面的模式有一部分是基于The Data Warehouse Toolkit里面的Grocery Store例子。
-- --主表-時(shí)間維以及銷(xiāo)售事實(shí)。 -- 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ù)時(shí)間的銷(xiāo)售。 -- 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í)候根新概要字段的函數(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 -- 計(jì)算增/減量 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)制,因?yàn)?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;