PostgreSQL觸發器

http://blog.csdn.net/neo_liu0000/article/details/6255623

1 觸發器概述


       觸發器的功能就是爲了解決這類問題而設計的,當你更新或查詢某個資料表時會觸動觸發器,觸發器就會照您所設計的流程,同步去插入、更新、刪除其他資料,你不再需要重複下達多次的SQL命令就能達成一連串資料的同步處理。


        觸發器是某個數據庫操作發生時被自動調用的函數。可以在INSERT、UPDATE或DELETE操作之前或之後調用觸發器。PostgreSQL支持兩種類型的觸發器,一種是數據行級觸發器,另外一種是語句級觸發器。對於數據行級的觸發器,觸發發觸發器的語句每操作一個數據行,它就被執行一次。對於語句級的觸發器,它只會被執行一次。


 


         創建觸發器以前,必須定義觸發器使用的函數。這個函數不能有任何參數,它的返回值的類型必須是trigger。函數定義好以後,用命令CREATE TRIGGER創建觸發器。多個觸發器可以使用同一個函數。


 


         觸發器按按執行的時間被分爲before觸發器和after觸發器。語句級的before觸發器在語句開始執行前被調用,語句級的after觸發器在語句開始執行結束後被調用。數據行級的before觸發器在操作每個數據行以前被調用,數據行級的after觸發器在操作每個數據行以後被調用。


 


          語句級的觸發器應該返回NULL。


 


         行級的before觸發器的返回值不同,它對觸發觸發器的操作的影響也不同。如果它返回NULL, 觸發這個觸發器的INSERT/UPDATE/DELETE命令不會被執行。如果行級的BEFORE觸發器返回非空的值,則INSERT/UPDATE/DELETE命令繼續執行。對於UPDATE和INSERT操作觸發的行級BEFORE觸發器,如果它返回的數據行與更新以後的或被插入的數據行不相同,則以觸發器返回的數據行作爲新的更新以後的數據行和被插入的數據行。


 


           行級after觸發器的返回值總是被忽略,可以返回NULL。


 


          如果同一表上同對同一個事件定義了多個觸發器,這些觸發器將按它們的名字的字母順序被觸發。對於行級before觸發器來說,前一個觸發器返回的數據行作爲後一個觸發器的輸入。如果任何一個行級before觸發器返回NULL,後面的觸發器將停止執行,觸發觸發器的INSERT/UPDATE/DELETE命令也不會被執行。


 


          行級before觸發器一般用於檢查和修改將被插入和更新的數據。行級after觸發器一般用於將表中被更新的數據記錄到其它的表中,或者檢查與其它的表中的數據是否是一致的。


 


           before觸發器的執行效率比after觸發器高,在before觸發器和after觸發器都能被使用的情況下,應該選擇before觸發器。


 


           一個觸發器在執行的過程中,如果執行了其它的SQL命令,可能會觸發其它的觸發器,這被稱作觸發器級聯。對於觸發器級聯的層次,系統沒有任何限制,但觸發器級聯可能會調用前面已經執行過的觸發器,從而引起死循環,系統不會檢測這種現象,定義觸發器的用戶應該保證這種現象不會發生。


 


           定義觸發器的時候,也可以爲它指定參數(在CREATE TRIGGER命令中中指定)。系統提供了特殊的接口來訪問這些參數。


 


          觸發器在被調用時,系統會自動傳遞一些數據給它,這些數據包括觸發觸發器的事件類型(例如INSERT或UPDATE),對於行級觸發器,還包括NEW數據行(對於INSERT和 UPDATE觸發器)和OLD數據行(對於UPDATE和DELETE觸發器)。每種可以用來書寫觸發器函數的語言都提供了取這些數據的方法。


 


          語句級別的觸發器在執行過程中無法查看該語句插入、刪除或更新的任何數據行。《PL/pgSQL教程》第8章中有觸發器的實例。


            還有一種特殊的觸發器叫約束觸發器,這種觸發器的執行時間可以被命令SET CONSTRAINTS控制,詳細信息參考《SQL命令手冊》對CREATE CONSTRAINT TRIGGER命令的解釋。 


 
2 數據可見規則
    觸發器在執行過程中,如果執行SQL命令訪問觸發器的父表中的數據,這些SQL命令遵循下面的數據可見規則,這些規則決定它們能否看見觸發觸發器的操作修改的表中的數據行:
(1)語句級的before觸發器在執行過程中,該語句的所有的對錶中的數據的更新對它都不可見。語句級的after觸發器在執行過程中,該語句的所有的對錶中的數據的更新對它都可見。


(2)行級before觸發器在執行過程中,前面所有的已經被同一個命令處理的數據行對它是可見的,但觸發該觸發器的數據行的更新操作的結果(插入、更新或刪除)對它是不可見的。行級after觸發器在執行過程中,前面所有的已經被同一個命令處理的數據行對它是可見的。




3 事例
 可以用PL/pgSQL 來寫觸發器過程。可以用命令CREATE FUNCTION創建一個觸發器過程,這個函數沒有任何參數,返回值的類型必須是trigger。使用命令CREATE TRIGGER來創建一個觸發器,通過TG_ARGV來傳遞參數給觸發器過程,下面會介紹TG_ARGV的用法。


 


    當一個PL/pgSQL 函數作爲一個觸發器被調用時,系統自動在最外層的塊創建一些特殊的變量。這些變量分別是:


(1)NEW


數據類型是RECORD。對於行級觸發器,它存有INSERT或UPDATE操作產生的新的數據行。對於語句級觸發器,它的值是NULL。


 


(2)OLD


數據類型是RECORD。對於行級觸發器,它存有被UPDATE或DELETE操作修改或刪除的舊的數據行。對於語句級觸發器,它的值是NULL。


 


(3)TG_NAME


數據類型是name,它保存實際被調用的觸發器的名字。


 


(4)TG_WHEN


數據類型是text,根據觸發器定義信息的不同,它的值是BEFORE 或AFTER。


 


(5)TG_LEVEL


數據類型是text,根據觸發器定義信息的不同,它的值是ROW或STATEMENT。


 


(6)TG_OP


數據類型是text,它的值是INSERT、UPDATE或DELETE,表示觸發觸發器的操作類型。


 


(7)TG_RELID


數據類型是oid,表示觸發器作用的表的oid。


 


(8)TG_RELNAME


數據類型是name,表示觸發器作用的表的名字。它與下面的變量TG_TABLE_NAME的作用是一樣的。


 


(9)TG_TABLE_NAME


數據類型是name,表示觸發器作用的表的名字。


 


(10)TG_TABLE_SCHEMA


數據類型是name,表示觸發器作用的表所在的模式。


 


(11)TG_NARGS


數據類型是integer,表示CREATE TRIGGER命令傳給觸發器過程的參數的個數。


 


(12)TG_ARGV[]


數據類型是text類型的數組。表示CREATE TRIGGER命令傳給觸發器過程的所有參數。下標從0開始。TG_ARGV[0]表示第一個參數,TG_ARGV[1]表示第二個參數,以此類推。 如果下標小於0或大於等於tg_nargs,將會返回一個空值。


 


    觸發器函數必須返回一個NULL或者一個記錄/數據行類型的變量,這個變量的結構必須與觸發器作用的表的結構一樣。


 


    對於行級的BEFORE觸發器,如果返回NULL,後面的觸發器將不會被執行,觸發這個觸發器的INSERT/UPDATE/DELETE命令也不會執行。如果行級的BEFORE觸發器返回非空的值,則INSERT/UPDATE/DELETE命令繼續執行。對於UPDATE和INSERT操作觸發的行級BEFORE觸發器,如果它返回的數據行與更新以後的或被插入的數據行不相同,則以觸發器返回的數據行作爲新的更新好的數據行和被插入的數據行。


 


    語句級的觸發器的返回值和AFTER類型的行級觸發器的返回值總是被忽略,沒有任何意義。


如果觸發器在執行的過程中遇到或者發出了錯誤,觸發觸發器的操作將被終止。


 


下面是一些觸發器實例:


 


(1)使用一個行級BEFORE觸發器檢查表emp的 被插入或跟新操作完成以後的數據行在列salary上的值是否大於0,列name是否不是空值: 


CREATE TABLE emp (


    empname text,


    salary integer,


    last_date timestamp,


    last_user text


);


 


CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$


    BEGIN


        -- Check that empname and salary are given


        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;


 


        -- Who works for us when she must pay for it?


        IF NEW.salary < 0 THEN


            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;


        END IF;


 


        -- Remember who changed the payroll when


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


 


(2)將表emp上被插入、刪除和跟新的數據行存到另一個表emp_audit中:


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


        --


        -- Create a row in emp_audit to reflect the operation performed on emp,


        -- make use of the special variable TG_OP to work out the operation.


        --


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


 


(3)這是一個更復雜的例子,表sales_fact存放着表time_dimension的彙總數據,利用表time_dimension上的一個行級BEFORE觸發器保持sales_fact和time_dimension中的數據同步。


 


--


-- Main tables - time dimension and sales fact.


--


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


 


--


-- Summary table - sales by time.


--


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


 


--


-- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.


--


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


 


        -- Work out the increment/decrement amount(s).


        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


 


            -- forbid updates that change the time_key -


            -- (probably not too onerous, as DELETE + INSERT is how most


            -- changes will be made).


            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;


 


 


        -- Insert or update the summary row with the new values.


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


 


(3)


 


目標:
當表alphas插入新行時,更新titles的alpha_at爲NOW()
當表alphas刪除行時,更新titles的alpha_at爲NULL


1、安裝plpgsql語言到數據庫
createlang plpgsql DATABASE
2、建立一個返回爲trigger的過程
CREATE OR REPLACE FUNCTION after_alphas_id() RETURNS trigger AS $BODY$
BEGIN
  IF( TG_OP='DELETE' ) THEN
    UPDATE titles SET alpha_at=null WHERE id=OLD.title_id;
  ELSE
    UPDATE titles SET alpha_at=NOW() WHERE id=NEW.title_id;
  END IF;
  RETURN NULL;
END;


$BODY$
  LANGUAGE 'plpgsql';
3、創建觸發器
CREATE TRIGGER after_alphas_id
  AFTER INSERT OR DELETE
  ON alphas
  FOR EACH ROW
  EXECUTE PROCEDURE after_alphas_id();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章