37.10. Æ®¸®°Å ÇÁ·Î½ÃÀú

PL/pgSQL´Â Æ®¸®°Å ÇÁ·Î½ÃÀúÀÇ Á¤ÀÇ¿¡ »ç¿ëÇÒ ¼ö ÀÖ½À´Ï´Ù. Æ®¸®°Å ÇÁ·Î½ÃÀú´Â, CREATE FUNCTION¸í·ÉÀ» »ç¿ëÇØ, trigger¶ó´Â ¹Ýȯ°ªÀÇ ÇüŸ¦ °¡Áø ÀÎÀÚ°¡ ¾ø´Â ÇÔ¼ö·Î¼­ ÀÛ¼ºµË´Ï´Ù. ±× ÇÔ¼ö´Â, ºñ·Ï, CREATE TRIGGER¿¡¼­ ÀÎÀÚ¸¦ ÃëÇϰí ÀÖ¾ú¾îµµ, ÀÎÀÚ¸¦ °¡ÁöÁö ¾Ê´Â °ÍÀÌ ¼±¾ðµÇ¾î¾ß ÇÕ´Ï´Ù. Æ®¸®°ÅÀÇ ÀÎÀÚ´Â, Èļú ÇÏ´Â ´ë·Î, TG_ARGV¸¦ ÅëÇØ °Ç³×¹Þ½À´Ï´Ù.

PL/pgSQLÇÔ¼ö°¡ Æ®¸®°Å·Î¼­ ºÒ·Á °¬À» °æ¿ì, ¸î°³ÀÇ Æ¯¼öÇÑ º¯¼ö°¡ ÀÚµ¿ÀûÀ¸·Î ÃÖ»ó ·¹º§ÀÇ ºí·ÏÀ¸·Î ÀÛ¼ºµË´Ï´Ù. ±×°ÍµéÀ» ÀÌÇÏ¿¡ ³ªÅ¸³À´Ï´Ù.

NEW

RECORDµ¥ÀÌÅÍÇü. ÀÌ º¯¼ö´Â Çà ·¹º§ÀÇ Æ®¸®°Å¿¡¼­ÀÇ INSERT/UPDATEÁ¶ÀÛ¿¡ ÀÇÇØ °»½ÅµÈ, »õ·Î¿î µ¥ÀÌÅͺ£À̽ºÀÇ ÇàÀ» º¸°ü À¯ÁöÇÕ´Ï´Ù. ÀÌ º¯¼ö´Â ¸í·É¹® ·¹º§ÀÇ Æ®¸®°Å¿¡¼­´Â NULLÀÔ´Ï´Ù.

OLD

RECORDµ¥ÀÌÅÍÇü. ÀÌ º¯¼ö´Â, Çà ·¹º§ÀÇ Æ®¸®°Å¿¡¼­ÀÇ UPDATE/DELETEÁ¶ÀÛ¿¡ ÀÇÇØ °»½ÅµÇ±â ÀüÀÇ µ¥ÀÌÅͺ£À̽ºÀÇ ÇàÀ» º¸°ü À¯ÁöÇÕ´Ï´Ù. ÀÌ º¯¼ö´Â ¸í·É¹® ·¹º§ÀÇ Æ®¸®°Å¿¡¼­´Â NULLÀÔ´Ï´Ù.

TG_NAME

nameµ¥ÀÌÅÍÇü. ½ÇÁ¦·Î ¹ßÇàµÈ Æ®¸®°ÅÀÇ À̸§À» °¡Áö´Â º¯¼ö.

TG_WHEN

textµ¥ÀÌÅÍÇü. Æ®¸®°ÅÀÇ Á¤ÀÇ¿¡ ÀÇÁ¸ÇÑ BEFORE ¶Ç´Â AFTER¶ó´Â ¹®ÀÚ¿­.

TG_LEVEL

textµ¥ÀÌÅÍÇü. Æ®¸®°ÅÀÇ Á¤ÀÇ¿¡ ÀÇÁ¸ÇÑ ROW ¶Ç´Â STATEMENT¶ó´Â ¹®ÀÚ¿­.

TG_OP

textµ¥ÀÌÅÍÇü. Æ®¸®°Å¸¦ ¹ßÇàÇÑ Á¶ÀÛÀ» ³ªÅ¸³»´Â, INSERT, UPDATE ¶Ç´Â DELETE¶ó´Â ¹®ÀÚ¿­.

TG_RELID

oidµ¥ÀÌÅÍÇü. ÀÌ Æ®¸®°Å È£ÃâÀÇ ±â¿øÀÌ µÇ´Â Å×À̺íÀÇ °´Ã¼ ID.

TG_RELNAME

nameµ¥ÀÌÅÍÇü. ÀÌ Æ®¸®°Å È£ÃâÀÇ ±â¿øÀÌ µÇ´Â Å×À̺íÀÇ À̸§. ÇâÈÄ, À̰ÍÀº ÆóÁöµÉ °Í °°½À´Ï´Ù. ´ë½Å¿¡ TG_TABLE_NAME¸¦ »ç¿ëÇØ ÁÖ¼¼¿ä.

TG_TABLE_NAME

nameµ¥ÀÌÅÍÇü. ÀÌ Æ®¸®°Å È£ÃâÀÇ ±â¿øÀÌ µÇ´Â Å×À̺íÀÇ À̸§.

TG_TABLE_SCHEMA

nameµ¥ÀÌÅÍÇü. ÀÌ Æ®¸®°Å È£ÃâÀÇ ±â¿øÀÌ µÇ´Â Å×À̺íÀÇ ½ºÅ°¸¶¸í.

TG_NARGS

integerÇü. CREATE TRIGGER¹®¿¡¼­ÀÇ Æ®¸®°Å ÇÁ·Î½ÃÀú¿¡°Ô ÁÙ ¼ö ÀÖ´Â ÀÎÀÚÀÇ ¼ö.

TG_ARGV[]

textÇüÀÇ ¹è¿­Çü. CREATE TRIGGER¹®¿¡¼­ÀÇ ÀÎÀÚ. ÀÌ À妽º´Â 0À¸·ÎºÎÅÍ ½ÃÀ۵˴ϴÙ. ¹«È¿ÀÎ À妽º(0 ¹Ì¸¸À̳ª tg_nargsÀÌ»ó)´Â, NULL°ªÀ̶ó´Â °á°ú°¡ µË´Ï´Ù.

Æ®¸®°Å ÇÔ¼ö´Â NULL ¶Ç´Â, Æ®¸®°ÅÀÇ ±Ù¿øÀÌ µÈ Å×À̺íÀÇ ±¸Á¶¸¦ Á¤È®ÇÏ°Ô °¡Áø ·¹ÄÚµå/ÇàÀ» µ¹·ÁÁÖ¾î¾ß ÇÕ´Ï´Ù.

BEFORE·Î¼­ ¹ßÇàµÈ Çà level trigger°¡ NULL¸¦ µ¹·ÁÁÖ´Â °æ¿ì¿¡´Â, Æ®¸®°Å °ü¸®ÀÚ¿¡ ½ÇÁ¦ÀÇ Çà¿¡ÀÇ Á¶ÀÛÀ» Ãë¼ÒÇϵµ·Ï ÅëÁöÇÕ´Ï´Ù (Áï, ±× ÈÄ¿¡ Æ®¸®°Å°¡ ¹ßÇàµÇÁö ¾Ê°í, ±× INSERT/UPDATE/DELETE´Â ±× Çà¿¡ ´ëÇØ¼­ ½ÇÇàµÇÁö ¾Ê½À´Ï´Ù). ºñNULL°ªÀ» µ¹·ÁÁÖ´Â °æ¿ì¿¡´Â, ±× Á¶ÀÛÀº ±× Çà°ªÀ¸·Î 󸮵˴ϴÙ. ¿ø·¡ÀÇ NEWÀÇ °ª°ú ´Ù¸¥ Çà°ªÀ» µ¹·ÁÁÖ´Â °ÍÀº, »ðÀÔ, °»½ÅµÇ´Â °ªÀ» º¯°æÇÏ´Â °Í¿¡ ÁÖÀÇÇØ ÁÖ¼¼¿ä (±×·¯³ª, DELETEÀÇ °æ¿ì´Â Á÷Á¢ ¿µÇâÀ» ÁÖÁö ¾Ê½À´Ï´Ù). ÀúÀåÇØ¾ß ÇÒ ÇàÀ» º¯°æÇϱâ À§Çؼ­, NEWÀÇ °³°³ÀÇ °ªÀ» Á÷Á¢ ¿Å°Ü³õ°í NEW¸¦ µ¹·ÁÁÖ´Â Àϵµ, »õ·Î¿î ·¹ÄÚµå/ÇàÀ» ¿ÏÀüÇÏ°Ô ÀÛ¼ºÇØ µ¹·ÁÁÖ´Â Àϵµ °¡´ÉÇÕ´Ï´Ù.

¸í·É¹® ·¹º§ÀÇ BEFORE ¶Ç´Â AFTERÆ®¸®°Å, Çà ·¹º§ÀÇ AFTERÆ®¸®°ÅÀÇ ¹Ýȯ°ªÀº Ç×»ó ¹«½ÃµË´Ï´Ù. NULL¶ó°í ÇØµµ »ó°üÇÏÁö ¾Ê½À´Ï´Ù. ±×·¯³ª, ÀÌ·¯ÇÑ Á¾·ùÀÇ Æ®¸®°Å·Î, ¿¡·¯¸¦ ¹ß»ý½ÃÄÑ Á¶ÀÛ Àüü¸¦ ÁߴܽÃŰ´Â °ÍÀÌ °¡´ÉÇÕ´Ï´Ù.

Example 37-2¿¡ PL/pgSQLÀÇ Æ®¸®°Å ÇÔ¼öÀÇ ¿¹¸¦ ³ªÅ¸³À´Ï´Ù.

Example 37-2. PL/pgSQLÆ®¸®°Å ÇÁ·Î½ÃÀú

ÀÌ Æ®¸®°ÅÀÇ ¿¹¿¡¼­´Â, Å×À̺íÀÇ ÇàÀÌ »ðÀÔ ¶Ç´Â °»½ÅµÇ¾úÀ» ¶§¿¡´Â ¹Ýµå½Ã, ÇöÀçÀÇ »ç¿ëÀÚ¸í°ú ½Ã°¢ÀÌ ±× Çà¿¡ µé¾îÀÖ´Â °ÍÀ» È®½ÇÈ÷ ÇÕ´Ï´Ù. ±×¸®°í, Á¾¾÷¿ø¸íÀÌ ÁÖ¾îÁø °ÍÀ¸·Î ±× ±Þ·á°¡ Á¤ÀÇ °ªÀÎ °ÍÀ» È®ÀÎÇÕ´Ï´Ù.

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

Å×ÀÌºí¿¡ À־ÀÇ º¯°æÀÇ ·Î±×¸¦ ÃëÇÏ´Â ´Ù¸¥ ¹æ¹ýÀº, »ðÀÔ, °»½Å ¶Ç´Â »èÁ¦ÀÇ °¢°¢¿¡ ´ëÇÑ ÇàÀ» º¸À¯ÇÏ´Â »õ Å×À̺íÀ» ÀÛ¼ºÇÏ´Â °ÍÀÔ´Ï´Ù. ÀÌ ¹æ¹ýÀº Å×ÀÌºí¿¡¼­ÀÇ º¯°æÀÇ °¨»ç¶ó°í »ý°¢ÇÒ ¼ö ÀÖ½À´Ï´Ù. Example 37-3´Â PL/pgSQL¿¡ ÀÇÇÑ °¨»ç Æ®¸®°Å ÇÁ·Î½ÃÀúÀÇ ÀϷʸ¦ ³ªÅ¸³À´Ï´Ù.

Example 37-3. A PL/pgSQL °¨»ç Æ®¸®°Å ÇÁ·Î½ÃÀú

ÀÌ Æ®¸®°ÅÀÇ ¿¹¿¡¼­´Â, empÅ×ÀÌºí¿¡ÀÇ ÇàÀÇ »ðÀÔ, °»½Å ¶Ç´Â »èÁ¦ÀÇ ¸ðµÎ°¡ emp_audit Å×À̺í¾È¿¡ È®½ÇÈ÷ ±â·Ï(Áï °¨»ç)µË´Ï´Ù. ÇöÀç ½Ã°¢°ú »ç¿ëÀÚ¸íÀº, ¼öÇàµÈ ¿¬»ê Á¾·ù¿Í ÇÔ²² 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
        --
	-- emp·Î ¼öÇàµÈ ¿¬»êÀ» ¹Ý¿µÇÏ´Â ÇàÀ» emp_audit¿¡ ÀÛ¼º
	-- ¿¬»êÀÇ Á¾·ù¸¦ °áÁ¤Çϱâ À§Çؼ­, Ư¼öÇÑ º¯¼ö 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; -- AFTER Æ®¸®°ÅÀ̹ǷÎ, °á°ú´Â ¹«½ÃµË´Ï´Ù
    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();

Æ®¸®°ÅÀÇ »ç¿ë ¸ñÀûÀÇ Çϳª´Â, ¾î´À Å×À̺íÀÇ ¿ä¾à Å×À̺í(µ¥ÀÌÅ͸¦ ¹ßÃéÇÑ Å×À̺í)À» À¯ÁöÇÏ´Â °ÍÀÔ´Ï´Ù. ¿ä¾à Å×À̺íÀº, ¿ø·¡ÀÇ Å×ÀÌºí¿¡ ´ë½ÅÇØ, ƯÁ¤ Á¾·ùÀÇ Äõ¸®¿¡ ´ëÇØ¼­ »ç¿ëÇÒ ¼ö ÀÖ¾î Á¾Á¾ ½ÇÇà ½Ã°£À» Å«ÆøÀ¸·Î Ãà¼ÒÇÕ´Ï´Ù. Åë»ó ÀÌ ¹æ¹ýÀº µ¥ÀÌÅÍ µµ¸Å»ó¿¡ »ç¿ëµÇ¾î ÃøÁ¤ ¶Ç´Â °üÃø µ¥ÀÌÅÍ(ÆÑÆ® Å×À̺íÀ̶ó°í ¸»ÇÕ´Ï´Ù)¸¦ ¸Å¿ì Å©°Ô ÇÒ ¼ö ÀÖ½À´Ï´Ù. µ¥ÀÌÅÍ ¿þ¾îÇϿ콺 ³»ÀÇ ÆÑÆ® Å×ÀÌºí¿¡ ´ëÇØ¼­ ¿ä¾à Å×À̺íÀ» À¯ÁöÇÏ´Â PL/pgSQLÀÇ Æ®¸®°Å ÇÁ·Î½ÃÀúÀÇ ¿¹¸¦ Example 37-4¿¡ ³ªÅ¸³À´Ï´Ù.

Example 37-4. A ¿ä¾à Å×À̺íÀ» À¯ÁöÇϱâ À§ÇÑ PL/pgSQLÆ®¸®°Å ÇÁ·Î½ÃÀú

¿©±â¿¡ ¸»ÇÏ´Â ½ºÅ°¸¶ÀÇ ÀϺδ Ralph Kimball¿¡ ÀÇÇÑ The Data Warehouse Toolkit ÀÇ Grocery Store ÀÇ ¿¹¿¡ ±Ù°ÅÇϰí ÀÖ½À´Ï´Ù.

--
-- time dimension¿Í 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);

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

--
-- °»½Å, »ðÀÔ ¹× »èÁ¦¿¡ ÀÇÇØ ¿ä¾à Å×À̺íÀÇ ¿­À» ¼öÁ¤ÇÏ´Â ÇÔ¼ö¿Í Æ®¸®°Å
--
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¸¦ º¯°æÇÏ´Â °»½ÅÀ» ±ÝÁöÇÕ´Ï´Ù
            -- (»èÁ¦ + »ðÀÔÀÇ ¹æ¹ý¿¡ ÀÇÇØ ´ëºÎºÐÀÇ º¯°æÀ» ½Ç½ÃÇϱ⠶§¹®¿¡
            -- ±×·¸°Ô ±ÍÂúÁö´Â ¾Ê½À´Ï´Ù).
            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_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
                    -- ¾Æ¹«°Íµµ ÇÏÁö ¾Ê½À´Ï´Ù
            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;