41.10. ТÑиггеÑнÑе ÑÑнкÑии
Ð PL/pgSQL можно ÑоздаваÑÑ ÑÑиггеÑнÑе ÑÑнкÑии, коÑоÑÑе бÑдÑÑ Ð²ÑзÑваÑÑÑÑ Ð¿Ñи изменениÑÑ
даннÑÑ
или ÑобÑÑиÑÑ
в базе даннÑÑ
. ТÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ ÑоздаÑÑÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾Ð¹ CREATE FUNCTION, пÑи ÑÑом Ñ ÑÑнкÑии не должно бÑÑÑ Ð°ÑгÑменÑов, а Ñипом возвÑаÑаемого знаÑÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¶ÐµÐ½ бÑÑÑ trigger (Ð´Ð»Ñ ÑÑиггеÑов, ÑÑабаÑÑваÑÑиÑ
пÑи изменениÑÑ
даннÑÑ
) или event_trigger (Ð´Ð»Ñ ÑÑиггеÑов, ÑÑабаÑÑваÑÑиÑ
пÑи ÑобÑÑиÑÑ
в базе). ÐÐ»Ñ ÑÑиггеÑов авÑомаÑиÑеÑки опÑеделÑÑÑÑÑ ÑпеÑиалÑнÑе локалÑнÑе пеÑеменнÑе Ñ Ð¸Ð¼ÐµÐ½Ð°Ð¼Ð¸ вида TG_, опиÑÑваÑÑие ÑÑловие, повлÑкÑее вÑзов ÑÑиггеÑа.имÑ
41.10.1. ТÑиггеÑÑ Ð¿Ñи изменении даннÑÑ
ТÑÐ¸Ð³Ð³ÐµÑ Ð¿Ñи изменении даннÑÑ
обÑÑвлÑеÑÑÑ ÐºÐ°Ðº ÑÑнкÑÐ¸Ñ Ð±ÐµÐ· аÑгÑменÑов и Ñ Ñипом ÑезÑлÑÑаÑа trigger. ÐамеÑÑÑе, ÑÑо ÑÑа ÑÑнкÑÐ¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° обÑÑвлÑÑÑÑÑ Ð±ÐµÐ· аÑгÑменÑов, даже еÑли ожидаеÑÑÑ, ÑÑо она бÑÐ´ÐµÑ Ð¿Ð¾Ð»ÑÑаÑÑ Ð°ÑгÑменÑÑ, заданнÑе в команде CREATE TRIGGER â Ñакие аÑгÑменÑÑ Ð¿ÐµÑедаÑÑÑÑ ÑеÑез TG_ARGV, как опиÑано ниже.
Ðогда ÑÑнкÑÐ¸Ñ Ð½Ð° PL/pgSQL ÑÑабаÑÑÐ²Ð°ÐµÑ ÐºÐ°Ðº ÑÑиггеÑ, в блоке веÑÑ Ð½ÐµÐ³Ð¾ ÑÑÐ¾Ð²Ð½Ñ Ð°Ð²ÑомаÑиÑеÑки ÑоздаÑÑÑÑ Ð½ÐµÑколÑко ÑпеÑиалÑнÑÑ Ð¿ÐµÑеменнÑÑ :
NEWТип даннÑÑ
RECORD. ÐеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ ÑодеÑÐ¶Ð¸Ñ Ð½Ð¾Ð²ÑÑ ÑÑÑÐ¾ÐºÑ Ð±Ð°Ð·Ñ Ð´Ð°Ð½Ð½ÑÑ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´INSERT/UPDATEв ÑÑиггеÑÐ°Ñ ÑÑÐ¾Ð²Ð½Ñ ÑÑÑоки. Ð ÑÑиггеÑÐ°Ñ ÑÑÐ¾Ð²Ð½Ñ Ð¾Ð¿ÐµÑаÑоÑа и Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ÑDELETEÑÑа пеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¸Ð¼ÐµÐµÑ Ð·Ð½Ð°Ñение null.OLDТип даннÑÑ
RECORD. ÐеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ ÑодеÑÐ¶Ð¸Ñ ÑÑаÑÑÑ ÑÑÑÐ¾ÐºÑ Ð±Ð°Ð·Ñ Ð´Ð°Ð½Ð½ÑÑ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´UPDATE/DELETEв ÑÑиггеÑÐ°Ñ ÑÑÐ¾Ð²Ð½Ñ ÑÑÑоки. Ð ÑÑиггеÑÐ°Ñ ÑÑÐ¾Ð²Ð½Ñ Ð¾Ð¿ÐµÑаÑоÑа и Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ÑINSERTÑÑа пеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¸Ð¼ÐµÐµÑ Ð·Ð½Ð°Ñение null.TG_NAMEТип даннÑÑ
name. ÐеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ ÑодеÑÐ¶Ð¸Ñ Ð¸Ð¼Ñ ÑÑабоÑавÑего ÑÑиггеÑа.TG_WHENТип даннÑÑ
text. СÑÑока, ÑодеÑжаÑаÑBEFORE,AFTERилиINSTEAD OF, в завиÑимоÑÑи Ð¾Ñ Ð¾Ð¿ÑÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑÑиггеÑа.TG_LEVELТип даннÑÑ
text. СÑÑока, ÑодеÑжаÑаÑROWилиSTATEMENT, в завиÑимоÑÑи Ð¾Ñ Ð¾Ð¿ÑÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑÑиггеÑа.TG_OPТип даннÑÑ
text. СÑÑока, ÑодеÑжаÑаÑINSERT,UPDATE,DELETEилиTRUNCATE, в завиÑимоÑÑи Ð¾Ñ Ñого, Ð´Ð»Ñ ÐºÐ°ÐºÐ¾Ð¹ опеÑаÑии ÑÑабоÑал ÑÑиггеÑ.TG_RELIDТип даннÑÑ
oid. OID ÑаблиÑÑ, Ð´Ð»Ñ ÐºÐ¾ÑоÑой ÑÑабоÑал ÑÑиггеÑ.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 ÑÑÐ¸Ð³Ð³ÐµÑ ÑÑÐ¾Ð²Ð½Ñ ÑÑÑоки возвÑаÑÐ°ÐµÑ NULL, Ñо вÑе далÑнейÑие дейÑÑÐ²Ð¸Ñ Ñ ÑÑой ÑÑÑокой пÑекÑаÑаÑÑÑÑ (Ñ. е. не ÑÑабаÑÑваÑÑ Ð¿Ð¾ÑледÑÑÑие ÑÑиггеÑÑ, команда INSERT/UPDATE/DELETE Ð´Ð»Ñ ÑÑой ÑÑÑоки не вÑполнÑеÑÑÑ). ÐÑли возвÑаÑаеÑÑÑ Ð½Ðµ NULL, Ñо далÑнейÑÐ°Ñ Ð¾Ð±ÑабоÑка пÑодолжаеÑÑÑ Ð¸Ð¼ÐµÐ½Ð½Ð¾ Ñ ÑÑой ÑÑÑокой. ÐозвÑаÑение ÑÑÑоки оÑлиÑной Ð¾Ñ Ð½Ð°ÑалÑной NEW, изменÑÐµÑ ÑÑÑокÑ, коÑоÑÐ°Ñ Ð±ÑÐ´ÐµÑ Ð²ÑÑавлена или изменена. ÐоÑÑомÑ, еÑли в ÑÑиггеÑной ÑÑнкÑии нÑжно вÑполниÑÑ Ð½ÐµÐºÐ¾ÑоÑÑе дейÑÑÐ²Ð¸Ñ Ð¸ не менÑÑÑ ÑÐ°Ð¼Ñ ÑÑÑокÑ, Ñо нÑжно возвÑаÑиÑÑ Ð¿ÐµÑеменнÑÑ NEW (или ÐµÑ ÑквиваленÑ). ÐÐ»Ñ Ñого ÑÑÐ¾Ð±Ñ Ð¸Ð·Ð¼ÐµÐ½Ð¸ÑÑ ÑоÑ
ÑанÑемÑÑ ÑÑÑокÑ, можно поменÑÑÑ Ð¾ÑделÑнÑе знаÑÐµÐ½Ð¸Ñ Ð² пеÑеменной NEW и заÑем ÐµÑ Ð²ÐµÑнÑÑÑ. Ðибо ÑоздаÑÑ Ð¸ веÑнÑÑÑ Ð¿Ð¾Ð»Ð½Ð¾ÑÑÑÑ Ð½Ð¾Ð²ÑÑ Ð¿ÐµÑеменнÑÑ. Ð ÑлÑÑае ÑÑÑоÑного ÑÑиггеÑа BEFORE Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ DELETE Ñамо возвÑаÑаемое знаÑение не Ð¸Ð¼ÐµÐµÑ Ð¿ÑÑмого ÑÑÑекÑа, но оно должно бÑÑÑ Ð¾ÑлиÑнÑм Ð¾Ñ NULL, ÑÑÐ¾Ð±Ñ Ð½Ðµ пÑеÑÑваÑÑ Ð¾Ð±ÑабоÑÐºÑ ÑÑÑоки. ÐбÑаÑиÑе внимание, ÑÑо пеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ NEW вÑегда NULL в ÑÑиггеÑаÑ
на DELETE, поÑÑÐ¾Ð¼Ñ Ð²Ð¾Ð·Ð²ÑаÑаÑÑ ÐµÑ Ð½Ðµ Ð¸Ð¼ÐµÐµÑ ÑмÑÑла. ТÑадиÑионной идиомой Ð´Ð»Ñ ÑÑиггеÑов DELETE ÑвлÑеÑÑÑ Ð²Ð¾Ð·Ð²ÑÐ°Ñ Ð¿ÐµÑеменной OLD.
ТÑиггеÑÑ INSTEAD OF (ÑÑо вÑегда ÑÑиггеÑÑ ÑÑÐ¾Ð²Ð½Ñ ÑÑÑок и они могÑÑ Ð¿ÑименÑÑÑÑÑ ÑолÑко Ñ Ð¿ÑедÑÑавлениÑми) могÑÑ Ð²Ð¾Ð·Ð²ÑаÑаÑÑ NULL, ÑÑÐ¾Ð±Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°ÑÑ, ÑÑо они не вÑполнÑли никакиÑ
изменений, Ñак ÑÑо обÑабоÑÐºÑ ÑÑой ÑÑÑоки можно не пÑодолжаÑÑ (Ñо еÑÑÑ, не вÑзÑваÑÑ Ð¿Ð¾ÑледÑÑÑие ÑÑиггеÑÑ Ð¸ не ÑÑиÑаÑÑ ÑÑÑÐ¾ÐºÑ Ð² ÑиÑле обÑабоÑаннÑÑ
ÑÑÑок Ð´Ð»Ñ Ð¾ÐºÑÑжаÑÑиÑ
команд INSERT/UPDATE/DELETE). РпÑоÑивном ÑлÑÑае должно бÑÑÑ Ð²Ð¾Ð·Ð²ÑаÑено знаÑение, оÑлиÑное Ð¾Ñ NULL, показÑваÑÑее, ÑÑо ÑÑÐ¸Ð³Ð³ÐµÑ Ð²Ñполнил запÑоÑеннÑÑ Ð¾Ð¿ÐµÑаÑиÑ. ÐÐ»Ñ Ð¾Ð¿ÐµÑаÑий INSERT и UPDATE возвÑаÑаемÑм знаÑением должно бÑÑÑ NEW, коÑоÑое ÑÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð¼Ð¾Ð¶ÐµÑ Ð¼Ð¾Ð´Ð¸ÑиÑиÑоваÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑжки пÑедложений INSERT RETURNING и UPDATE RETURNING (ÑÑо Ñакже повлиÑÐµÑ Ð½Ð° знаÑение ÑÑÑоки, пеÑедаваемое поÑледÑÑÑим ÑÑиггеÑам, или доÑÑÑпное под ÑпеÑиалÑнÑм пÑевдонимом EXCLUDED в опеÑаÑоÑе INSERT Ñ Ð¿Ñедложением ON CONFLICT DO UPDATE). ÐÐ»Ñ Ð¾Ð¿ÐµÑаÑий DELETE возвÑаÑаемÑм знаÑением должно бÑÑÑ OLD.
ÐозвÑаÑаемое знаÑение Ð´Ð»Ñ ÑÑÑоÑного ÑÑиггеÑа AFTER и ÑÑиггеÑов ÑÑÐ¾Ð²Ð½Ñ Ð¾Ð¿ÐµÑаÑоÑа (BEFORE или AFTER) вÑегда игноÑиÑÑеÑÑÑ. ÐÑо Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð¸ NULL. Ðднако в ÑÑÐ¸Ñ ÑÑиггеÑÐ°Ñ Ð¿Ð¾-пÑÐµÐ¶Ð½ÐµÐ¼Ñ Ð¼Ð¾Ð¶Ð½Ð¾ пÑеÑваÑÑ Ð²ÑзвавÑÑÑ Ð¸Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ, Ð´Ð»Ñ ÑÑого нÑжно Ñвно вÑзваÑÑ Ð¾ÑибкÑ.
ÐÑимеÑ 41.3 показÑÐ²Ð°ÐµÑ Ð¿ÑÐ¸Ð¼ÐµÑ ÑÑиггеÑной ÑÑнкÑии в PL/pgSQL.
ÐÑÐ¸Ð¼ÐµÑ 41.3. ТÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð½Ð° 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
-- ÐÑовеÑиÑÑ, ÑÑо ÑÐºÐ°Ð·Ð°Ð½Ñ Ð¸Ð¼Ñ ÑоÑÑÑдника и заÑплаÑа
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 FUNCTION emp_stamp();ÐÑÑгой ваÑÐ¸Ð°Ð½Ñ Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¶ÑÑнала изменений Ð´Ð»Ñ ÑаблиÑÑ Ð¿ÑÐµÐ´Ð¿Ð¾Ð»Ð°Ð³Ð°ÐµÑ Ñоздание новой ÑаблиÑÑ, коÑоÑÐ°Ñ Ð±ÑÐ´ÐµÑ ÑодеÑжаÑÑ Ð¾ÑделÑнÑÑ Ð·Ð°Ð¿Ð¸ÑÑ Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ вÑполненной ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ INSERT, UPDATE, DELETE. ÐÑÐ¾Ñ Ð¿Ð¾Ð´Ñ Ð¾Ð´ можно ÑаÑÑмаÑÑиваÑÑ ÐºÐ°Ðº пÑоÑоколиÑование изменений ÑаблиÑÑ Ð´Ð»Ñ Ð°ÑдиÑа. ÐÑимеÑ 41.4 показÑÐ²Ð°ÐµÑ ÑеализаÑÐ¸Ñ ÑооÑвеÑÑÑвÑÑÑей ÑÑиггеÑной ÑÑнкÑии в PL/pgSQL.
ÐÑÐ¸Ð¼ÐµÑ 41.4. ТÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð°ÑдиÑа в PL/pgSQL
ÐоказаннÑй в ÑÑом пÑимеÑе ÑÑÐ¸Ð³Ð³ÐµÑ Ð³Ð°ÑанÑиÑÑеÑ, ÑÑо лÑбое добавление, изменение или Ñдаление ÑÑÑоки в ÑаблиÑе 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
--
-- Ðобавление ÑÑÑоки в emp_audit, коÑоÑÐ°Ñ Ð¾ÑÑÐ°Ð¶Ð°ÐµÑ Ð¾Ð¿ÐµÑаÑиÑ, вÑполнÑемÑÑ Ð² emp,
-- Ð´Ð»Ñ Ð¾Ð¿ÑÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñипа опеÑаÑии пÑименÑеÑÑÑ ÑпеÑиалÑÐ½Ð°Ñ Ð¿ÐµÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ TG_OP.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), current_user, OLD.*;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), current_user, NEW.*;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), current_user, 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 FUNCTION process_emp_audit();У пÑедÑдÑÑего пÑимеÑа еÑÑÑ ÑазновидноÑÑÑ, коÑоÑÐ°Ñ Ð¸ÑполÑзÑÐµÑ Ð¿ÑедÑÑавление, ÑоединÑÑÑее оÑновнÑÑ ÑаблиÑÑ Ð¸ ÑаблиÑÑ Ð°ÑдиÑа, Ð´Ð»Ñ Ð¾ÑобÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð°ÑÑ Ð¿Ð¾Ñледнего Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑÑÑоки. ÐÑи ÑÑом Ð¿Ð¾Ð´Ñ Ð¾Ð´Ðµ по-пÑÐµÐ¶Ð½ÐµÐ¼Ñ Ð²ÐµÐ´ÑÑÑÑ Ð¿Ð¾Ð»Ð½Ñй жÑÑнал аÑдиÑа в оÑделÑной ÑаблиÑе, но Ñакже имееÑÑÑ Ð¿ÑедÑÑавление Ñ ÑпÑоÑеннÑм аÑдиÑоÑÑким Ñледом. ÐÑо пÑедÑÑавление ÑодеÑÐ¶Ð¸Ñ Ð²ÑеменнÑÑ Ð¼ÐµÑкÑ, коÑоÑÐ°Ñ Ð²ÑÑиÑлÑеÑÑÑ Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑÑÑоки из даннÑÑ Ð°ÑдиÑоÑÑкой ÑаблиÑÑ. ÐÑимеÑ 41.5 показÑÐ²Ð°ÐµÑ Ð¿ÑÐ¸Ð¼ÐµÑ ÑÑиггеÑа на пÑедÑÑавление Ð´Ð»Ñ Ð°ÑдиÑа в PL/pgSQL.
ÐÑÐ¸Ð¼ÐµÑ 41.5. ТÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð½Ð° PL/pgSQL Ð´Ð»Ñ Ð°ÑдиÑа в пÑедÑÑавлении
Ð ÑÑом пÑимеÑе ÑÑиггеÑ, ÑвÑзаннÑй Ñ Ð¿ÑедÑÑавлением, Ð´ÐµÐ»Ð°ÐµÑ ÑÑо пÑедÑÑавление изменÑемÑм и гаÑанÑиÑÑеÑ, ÑÑо лÑÐ±Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° на добавление, изменение или Ñдаление ÑÑÑоки в пÑедÑÑавлении бÑÐ´ÐµÑ Ð·Ð°Ð¿Ð¸Ñана Ð´Ð»Ñ Ð°ÑдиÑа в ÑаблиÑÑ emp_audit. Также запиÑÑваÑÑÑÑ Ð²ÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¼ÐµÑка, Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑзоваÑÐµÐ»Ñ Ð¸ Ñип вÑполнÑемой опеÑаÑии. ÐÑедÑÑавление показÑÐ²Ð°ÐµÑ Ð´Ð°ÑÑ Ð¿Ð¾Ñледнего Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑÑÑоки.
CREATE TABLE emp (
empname text PRIMARY KEY,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer,
stamp timestamp NOT NULL
);
CREATE VIEW emp_view AS
SELECT e.empname,
e.salary,
max(ea.stamp) AS last_updated
FROM emp e
LEFT JOIN emp_audit ea ON ea.empname = e.empname
GROUP BY 1, 2;
CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
BEGIN
--
-- ÐÑполниÑÑ ÑÑебÑемÑÑ Ð¾Ð¿ÐµÑаÑÐ¸Ñ Ð² emp и добавиÑÑ Ð² emp_audit ÑÑÑокÑ,
-- оÑÑажаÑÑÑÑ ÑÑÑ Ð¾Ð¿ÐµÑаÑиÑ.
--
IF (TG_OP = 'DELETE') THEN
DELETE FROM emp WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
OLD.last_updated = now();
INSERT INTO emp_audit VALUES('D', current_user, OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('U', current_user, NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp VALUES(NEW.empname, NEW.salary);
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('I', current_user, NEW.*);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
FOR EACH ROW EXECUTE FUNCTION update_emp_view();Ðдин из ваÑианÑов иÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑÑиггеÑов ÑÑо поддеÑжание в акÑÑалÑном ÑоÑÑоÑнии оÑделÑной ÑаблиÑÑ Ð¸Ñогов Ð´Ð»Ñ Ð½ÐµÐºÐ¾ÑоÑой ÑаблиÑÑ. РнекоÑоÑÑÑ ÑлÑÑаÑÑ Ð¾ÑделÑÐ½Ð°Ñ ÑаблиÑа Ñ Ð¸Ñогами Ð¼Ð¾Ð¶ÐµÑ Ð¸ÑполÑзоваÑÑÑÑ Ð² запÑоÑÐ°Ñ Ð²Ð¼ÐµÑÑо оÑновной ÑаблиÑÑ. ÐÑи ÑÑом заÑаÑÑÑÑ Ð²ÑÐµÐ¼Ñ Ð²ÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿ÑоÑов знаÑиÑелÑно ÑокÑаÑаеÑÑÑ. ÐÑа ÑÐµÑ Ð½Ð¸ÐºÐ° ÑиÑоко иÑполÑзÑеÑÑÑ Ð² Ñ ÑанилиÑÐ°Ñ Ð´Ð°Ð½Ð½ÑÑ , где ÑаблиÑÑ ÑакÑов могÑÑ Ð±ÑÑÑ Ð¾ÑÐµÐ½Ñ Ð±Ð¾Ð»ÑÑими. ÐÑимеÑ 41.6 демонÑÑÑиÑÑÐµÑ ÑÑиггеÑнÑÑ ÑÑнкÑÐ¸Ñ Ð½Ð° PL/pgSQL, коÑоÑÐ°Ñ Ð¿Ð¾Ð´Ð´ÐµÑÐ¶Ð¸Ð²Ð°ÐµÑ ÑаблиÑÑ Ð¸Ñогов Ð´Ð»Ñ ÑаблиÑÑ ÑакÑов в Ñ ÑанилиÑе даннÑÑ .
ÐÑÐ¸Ð¼ÐµÑ 41.6. ТÑиггеÑÐ½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð½Ð° PL/pgSQL Ð´Ð»Ñ Ð²ÐµÐ´ÐµÐ½Ð¸Ñ ÑаблиÑÑ Ð¸Ñогов
ÐÑедÑÑÐ°Ð²Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð´ÐµÑÑ ÑÑ ÐµÐ¼Ð° даннÑÑ ÑаÑÑиÑно оÑнована на пÑимеÑе Grocery Store из книги The Data Warehouse Toolkit РалÑÑа Ðимбалла (Ralph Kimball).
--
-- ÐÑновнÑе ÑаблиÑÑ: ÑаблиÑа вÑеменнÑÑ
пеÑиодов и ÑаблиÑа ÑакÑов пÑодаж
--
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);
--
-- ТаблиÑа Ñ Ð¸Ñогами пÑодаж по пеÑиодам
--
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);
--
-- ФÑнкÑÐ¸Ñ Ð¸ ÑÑÐ¸Ð³Ð³ÐµÑ Ð´Ð»Ñ Ð¿ÐµÑеÑÑÑÑа ÑÑолбÑов иÑогов пÑи вÑполнении
-- команд INSERT, UPDATE, 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
-- ÐÑÑиÑлиÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ðµ колиÑеÑÑва/ÑÑммÑ.
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 -
-- (ÑÑо огÑаниÑение не должно вÑзваÑÑ Ð½ÐµÑдобÑÑв, Ñак как
-- в оÑновном Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑдÑÑ Ð²ÑполнÑÑÑÑÑ Ð¿Ð¾ ÑÑ
еме DELETE + INSERT).
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 FUNCTION 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;ТÑиггеÑÑ AFTER Ñакже могÑÑ Ð¸ÑполÑзоваÑÑ Ð¿ÐµÑеÑ
однÑе ÑаблиÑÑ Ð´Ð»Ñ Ð¿ÑоÑмоÑÑа вÑего набоÑа ÑÑÑок, изменÑннÑÑ
опеÑаÑоÑом, вÑзвавÑим ÑÑиггеÑ. Ðоманда CREATE TRIGGER назнаÑÐ°ÐµÑ Ð¸Ð¼ÐµÐ½Ð° одной или обеим пеÑеÑ
однÑм ÑаблиÑам, а заÑем ÑÑнкÑÐ¸Ñ Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾ ÑÑим именам обÑаÑаÑÑÑÑ Ðº ним как к вÑеменнÑм ÑаблиÑам ÑолÑко Ð´Ð»Ñ ÑÑениÑ. ÐÑо иллÑÑÑÑиÑÑÐµÑ ÐÑимеÑ 41.7.
ÐÑÐ¸Ð¼ÐµÑ 41.7. ÐÑганизаÑÐ¸Ñ Ð°ÑдиÑа Ñ Ð¿ÐµÑÐµÑ Ð¾Ð´Ð½Ñми ÑаблиÑами
Рданном пÑимеÑе доÑÑигаеÑÑÑ ÑÐ¾Ñ Ð¶Ðµ ÑезÑлÑÑаÑ, ÑÑо и в ÐÑимеÑ 41.4, но вмеÑÑо ÑÑиггеÑа, ÑÑабаÑÑваÑÑего Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑÑÑоки, в нÑм иÑполÑзÑеÑÑÑ ÑÑиггеÑ, ÑÑабаÑÑваÑÑий ÐµÐ´Ð¸Ð½Ð¾Ð¶Ð´Ñ Ð´Ð»Ñ Ð¾Ð¿ÐµÑаÑоÑа и полÑÑаÑÑий нÑжнÑе ÐµÐ¼Ñ Ð´Ð°Ð½Ð½Ñе в пеÑеÑ
одной ÑаблиÑе. ÐÑо Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð³Ð¾Ñаздо бÑÑÑÑее, Ñем ваÑÐ¸Ð°Ð½Ñ Ñ Ð¿Ð¾ÑÑÑоÑнÑм ÑÑиггеÑом, когда Ñелевой опеÑаÑÐ¾Ñ Ð¸Ð·Ð¼ÐµÐ½ÑÐµÑ ÑÑÐ°Ð·Ñ Ð¼Ð½Ð¾Ð¶ÐµÑÑво ÑÑÑок. ÐамеÑÑÑе, ÑÑо Ð¼Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ Ð¾Ð±ÑÑвиÑÑ Ð¾ÑделÑнÑе ÑÑиггеÑÑ Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ вида ÑобÑÑиÑ, Ñак как пÑÐµÐ´Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ REFERENCING в каждом ÑлÑÑае бÑдÑÑ ÑазнÑми. Ðо ÑÑо не меÑÐ°ÐµÑ Ð¿Ñи желании иÑполÑзоваÑÑ Ð¾Ð´Ð½Ñ ÑÑиггеÑнÑÑ ÑÑнкÑиÑ. (Ðа пÑакÑике Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð»ÑÑÑе иÑполÑзоваÑÑ ÑÑи оÑделÑнÑе ÑÑнкÑии и не пÑовеÑÑÑÑ TG_OP во вÑÐµÐ¼Ñ Ð²ÑполнениÑ.)
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, коÑоÑÑе оÑÑажаÑÑ Ð¾Ð¿ÐµÑаÑии, вÑполнÑемÑе в emp,
-- Ð´Ð»Ñ Ð¾Ð¿ÑÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñипа опеÑаÑий пÑименÑеÑÑÑ ÑпеÑиалÑÐ½Ð°Ñ Ð¿ÐµÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ TG_OP.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit
SELECT 'D', now(), current_user, o.* FROM old_table o;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit
SELECT 'U', now(), current_user, n.* FROM new_table n;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit
SELECT 'I', now(), current_user, n.* FROM new_table n;
END IF;
RETURN NULL; -- возвÑаÑаемое знаÑение Ð´Ð»Ñ ÑÑиггеÑа AFTER игноÑиÑÑеÑÑÑ
END;
$emp_audit$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit_ins
AFTER INSERT ON emp
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_upd
AFTER UPDATE ON emp
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_del
AFTER DELETE ON emp
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();41.10.2. ТÑиггеÑÑ ÑобÑÑий
Ð PL/pgSQL можно ÑоздаваÑÑ ÑобÑÑийнÑе ÑÑиггеÑÑ. Postgres Pro ÑÑебÑеÑ, ÑÑÐ¾Ð±Ñ ÑÑнкÑиÑ, коÑоÑÐ°Ñ Ð²ÑзÑваеÑÑÑ ÐºÐ°Ðº ÑобÑÑийнÑй ÑÑиггеÑ, обÑÑвлÑлаÑÑ Ð±ÐµÐ· аÑгÑменÑов и Ñипом возвÑаÑаемого знаÑÐµÐ½Ð¸Ñ Ð±Ñл event_trigger.
Ðогда ÑÑнкÑÐ¸Ñ Ð½Ð° PL/pgSQL вÑзÑваеÑÑÑ ÐºÐ°Ðº ÑобÑÑийнÑй ÑÑиггеÑ, в блоке веÑÑ Ð½ÐµÐ³Ð¾ ÑÑÐ¾Ð²Ð½Ñ Ð°Ð²ÑомаÑиÑеÑки ÑоздаÑÑÑÑ Ð½ÐµÑколÑко ÑпеÑиалÑнÑÑ Ð¿ÐµÑеменнÑÑ :
TG_EVENTТип даннÑÑ
text. СÑÑока, ÑодеÑжаÑÐ°Ñ ÑобÑÑие, Ð´Ð»Ñ ÐºÐ¾ÑоÑого ÑÑабоÑал ÑÑиггеÑ.TG_TAGТип даннÑÑ
text. ÐеÑеменнаÑ, ÑодеÑжаÑÐ°Ñ Ñег командÑ, Ð´Ð»Ñ ÐºÐ¾ÑоÑой ÑÑабоÑал ÑÑиггеÑ.
ÐÑимеÑ 41.8 демонÑÑÑиÑÑÐµÑ ÑеализаÑÐ¸Ñ ÑÑнкÑии ÑобÑÑийного ÑÑиггеÑа на PL/pgSQL.
ÐÑÐ¸Ð¼ÐµÑ 41.8. ФÑнкÑÐ¸Ñ ÑобÑÑийного ÑÑиггеÑа на PL/pgSQL
ТÑÐ¸Ð³Ð³ÐµÑ Ð² ÑÑом пÑимеÑе пÑоÑÑо вÑдаÑÑ ÑообÑение NOTICE каждÑй Ñаз, когда вÑполнÑеÑÑÑ Ð¿Ð¾Ð´Ð´ÐµÑÐ¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°.
CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();