×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

При отключении обработки cookie наш сайт продолжит функционировать, однако будут использоваться исключительно необходимые технические файлы, без которых работа ресурса невозможна.

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

5.6.10. Триггерные функции

5.6.10.1. Триггеры на изменения данных
5.6.10.2. Триггеры событий

PL/pgSQL можно использовать для определения триггерных функций, реагирующих на изменения данных или события базы данных. Триггерная функция создается с помощью команды CREATE FUNCTION , при этом она объявляется как функция без аргументов с типом возвращаемого значения триггер (для триггеров изменения данных) или event_trigger (для триггеров событий базы данных). Специальные локальные переменные с именами TG_something определяются автоматически для описания условия, послужившего причиной вызова.

5.6.10.1. Триггеры на изменения данных #

A триггер изменения данных объявляется как функция без аргументов, возвращающая тип данных триггер. Необходимо учитывать, что функция должна быть объявлена без аргументов, даже если предполагается получение аргументов, указанных в команде CREATE TRIGGER — такие аргументы передаются через переменную TG_ARGV, как описано ниже.

Когда PL/pgSQL функция вызывается в качестве триггера, в блоке верхнего уровня автоматически создается несколько специальных переменных. Среди них:

NEW record #

новая строка базы данных для INSERT/UPDATE операций в триггерах уровня строки. Данная переменная имеет значение null в триггерах уровня оператора и для DELETE операций.

OLD record #

старая строка таблицы для UPDATE/DELETE операций в триггерах уровня строки. Данная переменная имеет значение null в триггерах уровня оператора и для INSERT операций.

TG_NAME имя #

имя сработавшего триггера.

TG_WHEN text #

BEFORE, AFTER, или INSTEAD OF, в зависимости от определения триггера.

TG_LEVEL text #

ROW или STATEMENT, в зависимости от определения триггера.

TG_OP text #

операция, вызвавшая срабатывание триггера: INSERT, UPDATE, DELETE, или TRUNCATE.

TG_RELID oid (ссылается на pg_class.oid) #

идентификатор объекта таблицы, вызвавшей срабатывание триггера.

TG_RELNAME имя #

таблица, вызвавшая срабатывание триггера срабатывание. Данная возможность является устаревшей и может быть удалена в одном из будущих выпусков. Используйте TG_TABLE_NAME вместо неё.

TG_TABLE_NAME имя #

таблица, вызвавшая срабатывание триггера.

TG_TABLE_SCHEMA имя #

схема таблицы, вызвавшей срабатывание триггера.

TG_NARGS integer #

количество аргументов, переданных триггерной функции в CREATE TRIGGER операторе.

TG_ARGV text[] #

аргументы из the CREATE TRIGGER операторе. Индексация начинается с 0. Некорректные индексы (меньше 0 или больше либо равные tg_nargs) приводят к значению NULL.

Триггерная функция должна возвращать либо NULL или значение типа record/row, имеющее структуру, точно соответствующую структуре таблицы, для которой сработал триггер.

Триггеры уровня строки, вызываемые BEFORE может возвращать значение null, чтобы сигнализировать менеджеру триггеров о необходимости пропустить оставшуюся часть операции для данной строки (т. е. последующие триггеры не вызываются, и INSERT/UPDATE/DELETE не выполняется для этой строки). Если возвращается значение, отличное от null, выполнение операции продолжается с данным значением строки. Возврат значения строки, отличного от исходного значения параметра NEW изменяет строку, которая будет вставлена или обновлена. Таким образом, если функция триггера должна обеспечить нормальное выполнение вызывающего действия без изменения значения строки, 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. Однако любой из этих типов триггеров всё же может прервать выполнение всей операции, вызвав ошибку.

Пример 5.6.3 приведён пример триггерной функции на языке PL/pgSQL.

Пример 5.6.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
        -- Проверка заполнения имени сотрудника (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 FUNCTION emp_stamp();

Другой способ логирования изменений в таблице подразумевает создание новой таблицы, содержащей записи о каждой выполненной операции вставки, обновления или удаления. Такой подход можно рассматривать как аудит изменений в таблице. Пример 5.6.4 приведен пример триггерной функции для аудита в PL/pgSQL.

Пример 5.6.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();

В одном из вариантов предыдущего примера используется представление, объединяющее основную таблицу с таблицей аудита, для отображения времени последнего изменения каждой записи. Данный подход по-прежнему позволяет фиксировать полный журнал аудита изменений в таблице, но при этом предоставляет упрощенное представление журнала, в котором для каждой записи отображается только временная метка последнего изменения, полученная из данных аудита. Пример 5.6.5 приведен пример триггера аудита для представления в PL/pgSQL.

Пример 5.6.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
        -- для отражения изменений, внесенных в таблицу emp.
        --
        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();

Одним из назначений триггеров является поддержка сводной таблицы на основе данных из другой таблицы. Полученную сводную таблицу можно использовать вместо исходной при выполнении определенных запросов — зачастую со значительно сокращенным временем выполнения. Этот метод обычно используется в хранилищах данных, где таблицы измеренных или наблюдаемых данных (так называемые таблицы фактов) могут иметь чрезвычайно большой объем. Пример 5.6.6 приведён пример триггерной функции на языке PL/pgSQL поддерживающая сводную таблицу для таблицы фактов в хранилище данных.

Пример 5.6.6. Оператор PL/pgSQL Триггерная функция для поддержки сводной таблицы

Описанная здесь схема частично основана на примере «Продуктовый магазин» из книги The Data Warehouse Toolkit Ральфа Кимболла.

--
-- Основные таблицы — измерение времени и факты продаж.
--
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);

--
-- Функция и триггер для обновления сводных столбцов при операциях 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

        -- Вычисление величины приращения или уменьшения.
        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 'Обновление time_key: % -> % не допускается',
                                                      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;


        -- Вставка или обновление итоговой строки с новыми значениями.
        <>
        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 команда присваивает имена одной или обеим переходным таблицам, после чего функция может обращаться к этим именам как к временным таблицам, доступным только для чтения. Пример 5.6.7 приведен пример.

Пример 5.6.7. Аудит с использованием переходных таблиц

Данный пример приводит к тем же результатам, что и Пример 5.6.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();

5.6.10.2. Триггеры событий #

PL/pgSQL можно использовать для определения событийные триггеры. Digital Q.DataBase требует, чтобы функция, вызываемая в качестве триггера событий, была объявлена без аргументов и возвращала тип event_trigger.

Если PL/pgSQL функция вызывается как событийный триггер, в блоке верхнего уровня автоматически создается несколько специальных переменных, а именно:

TG_EVENT text #

событие, для которого вызывается триггер.

TG_TAG text #

тег команды, для которой вызывается триггер.

Пример 5.6.8 содержит пример функции событийного триггера на языке PL/pgSQL.

Пример 5.6.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();

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению