Семинар 8. Хранимые функции, процедуры, триггеры

Содержание:

  1. Функции
  2. Интеграция с другими ЯП
  3. Процедуры
  4. Триггеры
  5. Полезные ссылки
  6. Практическое задание

Теоретическая справка

Хранимый код — объект базы данных, представляющий собой набор SQL-инструкций (и не только их), который компилируется один раз и хранится на сервере, т.е. функции, процедуры, триггеры.

Зачем нужен хранимый код?

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

1. Функции

PostgreSQL предоставляет большое количество встроенных функций. Например:

-- Функция повтора строки
-- repeat (text, int) → text
SELECT repeat('Pg', 4);  -- 'PgPgPgPg'

-- Экспонента
-- exp (double precision) → double precision
SELECT exp(1.0);  -- 2.7182818284590452

-- Функция перевода строки в верхний регистр
-- upper (text) → text
SELECT upper('hello world');  -- HELLO WORLD

Весь список можно посмотреть здесь.

Собственные функции можно писать не только на SQL, но и на других ЯП, об этом в разделе Интеграция с другими ЯП. Они определяются командой CREATE FUNCTION или CREATE OR REPLACE FUNCTION:

CREATE FUNCTION somefunc(int, text) RETURNS int
AS 'тело функции'
LANGUAGE [SQL | plpgsql | plpython | ...];

Обращение к аргументам функции осуществляется через $1, $2 и т.д или через алиасы самих аргументов, если они указаны. Простейший вариант функции на SQL:

-- Определение функции
CREATE FUNCTION add(int, int) RETURNS int
AS 'SELECT $1 + $2;'
LANGUAGE SQL;

-- Запрос
SELECT add(1, 3);  -- 4
---- Запрос к таблице

-- Создаём таблицу
CREATE TABLE test_t(a int, b int);

-- Добавляем значения
INSERT INTO test_t(a, b)
VALUES
(3, 10),
(5, 20);

-- Запрос с использованием определенной функции
SELECT add(a, b)
FROM test_t;

--- Ответ
-- |add|
-- | 13|
-- | 25|

NOTE:

Если писать CREATE FUNCTION, тело функции представляет собой просто текстовую строку. Часто для написания тела функции удобнее заключать эту строку в доллары, а не в обычные апострофы. Если не применять заключение в доллары, все апострофы или обратные косые черты в теле функции придётся экранировать, дублируя их.

1.1. Функции SQL со значениями аргументов по умолчанию

Значения аргументов по умолчанию определяются ключевым словом DEFAULT:

CREATE FUNCTION foo(a int, b int DEFAULT 2, c int DEFAULT 3) RETURNS int AS $$
    SELECT a + b + c;
$$ LANGUAGE SQL;

SELECT foo(10, 20, 30);  -- 60

SELECT foo(10, 20);  -- 33

SELECT foo(10);  -- 15

Вместо ключевого слова DEFAULT можно использовать знак =.

1.2. Функции SQL, возвращающие нескольких значений

Функция, возвращающая запись с несколькими выходными параметрами (составной тип):

CREATE FUNCTION dup(in int, out f1 int, out f2 text) AS $$
    -- Здесь CAST(выражение AS тип) приводит $1 к текстовому типу
    SELECT $1, CAST($1 AS text) || ' is text'
$$ LANGUAGE SQL;

-- Запрос
SELECT * FROM dup(42);

-- Ответ
-- |  f1  |    f2      |
-- |  42  | 42 is text |

То же самое можно сделать более развёрнуто, явно объявив составной тип:

CREATE TYPE dup_result AS (f1 int, f2 text);

CREATE FUNCTION dup(int) RETURNS dup_result
    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
LANGUAGE SQL;

SELECT * FROM dup(42);

1.3. Функции SQL, возвращающие таблицы

Ещё один способ вернуть несколько столбцов — использовать синтаксис RETURNS TABLE:

CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text)
    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
LANGUAGE SQL;

Однако запись RETURNS TABLE не позволяет явно указывать OUT и INOUT для параметров — все выходные столбцы необходимо записать в списке TABLE.

1.4. Функции SQL, модифицирующие таблицы

Помимо запросов SELECT, команды внутри функции могут включать запросы, изменяющие данные (INSERT, UPDATE, DELETE, MERGE и т.д.):

CREATE TABLE bank(account_id int, balance numeric);

CREATE FUNCTION debit_bank_acc(account_id int, debit numeric) RETURNS numeric AS $$
   UPDATE bank
    SET balance = balance - debit
    WHERE account_id = debit_bank_acc.account_id;
   
   SELECT balance
   FROM bank
   WHERE account_id = debit_bank_acc.account_id
$$ LANGUAGE SQL;

1.5. Модификаторы функций

Каждая функция имеет свою степень/категорию изменчивости: VOLATILE, STABLE, IMMUTABLE. Для лучшей эксплуатации оптимизатора следует использовать ту допустимую категорию, которая дает больше гарантий о своем поведении.

1. VOLATILE (Изменчивая)

Значение по умолчанию в CREATE FUNCTION. Функция не дает никаких гарантий о своем поведении: поведение может быть недетерминированно при одинаковых аргументах, модифицировать внутренне состояние базы данных, и т.д. Оптимизатор не делает никаких предположений касаемо поведения такой функции. Запросы, использующие VOLATILE функции будут пересчитывать значение функции на каждой применяемой строке таблицы.

Примеры функций: random(), currval(), timeofday().

SELECT random() = random();  -- false

2. STABLE (Стабильная)

Функция дает гарантию, что не модифицирует внутреннее состояние базы и гарантирует детерминированное поведение на одних и тех же аргументах в рамках одного запроса. Поэтому данная степень изменчивости позволяет оптимизировать множественные вызовы путем кеширования. В частности, такие функции можно использовать для поиска по индексу (для VOLATILE такое запрещено).

Примеры функций: now(), current_setting(), current_timestamp().

-- Не меняются в рамках одного запроса/транзакции
SELECT now() = now();  -- true
-- Но:
SELECT now();  -- 2025-01-12 15:21:51.240537 +00:00
SELECT now();  -- 2025-01-12 15:21:51.567379 +00:00

3. IMMUTABLE (Постоянная)

Функция дает те же гарантии, что и STABLE, но снимает ограничение на рамки одного запроса. Поэтому оптимизатор может предварительно “прогреть кэш” на константных аргументах.

Примеры функций: abs(x), lower(text).

Другие модификаторы

  • CALLED ON NULL INPUT - функция вызывается даже, если входной параметр равен NULL;
  • RETURNS NULL ON NULL INPUT - функция возвращает NULL, если хотя бы один из её аргументов равен NULL;
  • STRICT - функция должна вернуть ровно одну строку, или произойдёт ошибка во время выполнения;
  • LEAKPROOF - функция не раскрывает информацию через ошибки (для безопасности).

Пример:

CREATE OR REPLACE FUNCTION calculate_circle_area(radius double precision) RETURNS double precision AS $$
    SELECT PI() * radius * radius;
$$ LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT;

SELECT calculate_circle_area(1);  -- 3.14159265358979

2. Интеграция с другими ЯП

PostgreSQL позволяет разрабатывать собственный хранимый код и на других языках, отличных от SQL (на C также, но в рамках курса не рассматриваем). Эти другие языки обычно называются процедурными языками (PL, Procedural Languages). В рамках курса мы рассмотрим:

2.1. PL/pgSQL

PL/pgSQL — язык программирования, используемый для написания хранимого кода PostgreSQL. С помощью данного расширения можно писать выполняемые блоки, функции и особый объект баз данных – триггеры.

2.1.1. Структура функций

Тела функций на PL/pgSQL состоят из блоков. Структура блока (в […] указаны опциональные команды):

[ <<метка>> ]
[ DECLARE объявления ]
BEGIN
    операторы
END [ метка ];

Пример:

CREATE FUNCTION add_one (int) RETURNS int AS $$
    BEGIN
        RETURN $1 + 1;
    END;
$$ LANGUAGE plpgsql;

DECLARE определяет внутренние переменные функции:

CREATE FUNCTION add_one (int) RETURNS int AS $$
    <<block1>> -- Метка блока
    DECLARE
        delta int = 1;
    BEGIN
        RETURN $1 + delta;
    END;
$$ LANGUAGE plpgsql;

Метка используется, когда нужно дополнить имена переменных, объявленных в этом блоке. Явно это будет показано в примере с вложенными блоками ниже. Если метка указана после END, то она должна совпадать с меткой в начале блока.

2.1.2. Вложенные блоки кода

Вложенные блоки используются для логической группировки нескольких операторов или локализации области действия переменных для группы операторов.

Во время выполнения вложенного блока переменные, объявленные в нём, скрывают переменные внешних блоков с такими же именами. Чтобы получить доступ к внешним переменным, нужно дополнить их имена меткой блока.

CREATE FUNCTION somefunc() RETURNS int AS $$
<< outerblock >>
DECLARE
    quantity int = 30;
BEGIN
    RAISE NOTICE 'Сейчас quantity = %', quantity;  -- Выводится 30
    quantity = 50;
    
    -- Вложенный блок
    DECLARE
        quantity int = 80;
    BEGIN
        RAISE NOTICE 'Сейчас quantity = %', quantity;  -- Выводится 80
        RAISE NOTICE 'Во внешнем блоке quantity = %', outerblock.quantity;  -- Выводится 50
    END;

    RAISE NOTICE 'Сейчас quantity = %', quantity;  -- Выводится 50

    RETURN quantity;
END;
$$ LANGUAGE plpgsql;

NOTES:

Каждое логическое действие, будь то DECLARE, RETURN, RAISE, … (BEGIN таковым не является), в блоке должно завершаться символом ;. Каждый вложенный блок, также должен иметь точку с запятой после END, как показано выше. Однако финальный END, завершающий тело функции, не требует ;.

Ключевые слова не чувствительны к регистру символов. Как и в обычных SQL-командах, идентификаторы неявно преобразуются к нижнему регистру, если они не взяты в двойные кавычки.

2.1.3. Блочные комментарии

Блочный комментарий начинается с /* и завершается */. Они также могут быть вложенными.

CREATE FUNCTION add_ten (int) RETURNS int AS $$
    -- Комментарий 1

    /*
     Комментарий 2
     Комментарий 2
    */

    /*
     Комментарий 3
     Комментарий 3
        /*
         Вложенный комментарий 3
        */
    */
    BEGIN
        RETURN $1 + 10;
    END;
$$ LANGUAGE plpgsql;

2.1.4. Операторы PL/pgSQL

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

Условные операторы

PL/pgSQL поддерживает классические IF ... THEN и CASE ... WHEN выражения по аналогии с обычным SQL.

CREATE OR REPLACE FUNCTION classify_number(number numeric) RETURNS text AS $$
DECLARE
   result text;
BEGIN
   IF number = 0 THEN
      result = 'zero';
   ELSIF number > 0 THEN
      result = 'positive';
   ELSIF number < 0 THEN
      result = 'negative';
   ELSE
      result = 'NULL';
   END IF;

   RETURN result;
END;
$$ LANGUAGE plpgsql;

Выполнение динамических запросов

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

EXECUTE строка-команды 
    [ INTO цель ] 
    [ USING выражение [, ... ] ];

Строка-команды — это команды типа text, которую нужно выполнить. Необязательная цель — это то, куда будут помещены результаты команды. Необязательные выражения в USING формируют значения, которые будут вставлены в команду.

В тексте команды можно использовать значения параметров, ссылки на параметры обозначаются как $1, $2 и т.д. Эти символы указывают на значения, находящиеся в команде USING. Пример:

DO $$
DECLARE
    c int; -- Переменная для хранения результата
    checked_user text = 'user123';
    checked_date date = '2001-09-11';
BEGIN
    -- Выполнение динамического запроса
    EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
        INTO c
        USING checked_user, checked_date;
    
    RAISE NOTICE 'Count: %', c;
END;
$$;

DO здесь выполняет анонимный блок кода, то есть не сохраняет его в базе как функцию, а сразу исполняет один раз и выводит в консоль.

Циклы

Синтаксис цикла:

[<<метка>>]
LOOP
    операторы
END LOOP [ метка ];
  • EXIT:
EXIT [ метка ] [WHEN логическое-выражение];

При наличии WHEN цикл прекращается, только если логическое-выражение истинно. В противном случае управление переходит к оператору, следующему за EXIT.

  • CONTINUE:
CONTINUE [ метка ] [WHEN логическое-выражение];

При наличии WHEN следующая итерация цикла начинается только тогда, когда логическое-выражение истинно. В противном случае управление переходит к оператору, следующему за CONTINUE.

  • WHILE:
DO $$
DECLARE
    counter int = 1;
BEGIN
    WHILE counter < 1000 LOOP
        counter = counter * 2;
        RAISE NOTICE 'Counter: %', counter;
    END LOOP;
END;
$$;
  • Цикл FOR по целым числам:
DO $$
DECLARE
    start_value int = 20;
    end_value int = 0;
    step int = 2;
    i int;
BEGIN
    -- Цикл с start_value по end_value с шагом step в обратном порядке
    FOR i IN REVERSE start_value..end_value BY step LOOP
        RAISE NOTICE 'Current value: %', i;
    END LOOP;
END;
$$;
  • Цикл FOR по результатам запроса:
DO $$
DECLARE
    rec RECORD; -- Переменная для хранения текущей строки
BEGIN
    FOR rec IN SELECT id, name FROM mytable WHERE active = TRUE LOOP
        RAISE NOTICE 'ID: %, Name: %', rec.id, rec.name;
    END LOOP;
END;
$$;
  • Цикл FOREACH по массиву:
DO $$
DECLARE
    numbers int[] = ARRAY[10, 20, 30, 40, 50];
    num int;
BEGIN
    FOREACH num IN ARRAY numbers LOOP
        RAISE NOTICE 'Number: %', num;
    END LOOP;
END;
$$;
NOTE про транзакции

Важно не путать использование BEGIN/END для группировки операторов в PL/pgSQL с одноимёнными SQL-командами для управления транзакциями. BEGIN/END в PL/pgSQL служат только для группировки предложений. Они не начинают и не заканчивают транзакции. Про управление транзакциями в PL/pgSQL можно почитать в документации. Кроме того, блок с командой EXCEPTION по сути создаёт вложенную транзакцию, которую можно отменить, не затрагивая внешнюю транзакцию.

2.2. PL/Python

Как установить PL/Python

Прежде чем что-то использовать, это надо установить. Если вы используете контейнер с postgres поверх apline, то скачайте пакеты:

apk add --no-cache --virtual .plpython3-deps --repository http://nl.alpinelinux.org/alpine/edge/testing \
    postgresql-plpython3 \
    && ln -s /usr/lib/postgresql/plpython3.so /usr/local/lib/postgresql/plpython3.so \
    && ln -s /usr/share/postgresql/extension/plpython3u.control /usr/local/share/postgresql/extension/plpython3u.control \
    && ln -s /usr/share/postgresql/extension/plpython3u--1.0.sql /usr/local/share/postgresql/extension/plpython3u--1.0.sql \
    && ln -s /usr/share/postgresql/extension/plpython3u--unpackaged--1.0.sql /usr/local/share/postgresql/extension/plpython3u--unpackaged--1.0.sql

После установите PL/Python в определённую базу данных, выполнив команду:

CREATE EXTENSION plpython3u;  -- Будем использовать Python3

Функции на PL/Python объявляются стандартным образом с помощью команды CREATE FUNCTION:

CREATE FUNCTION имя_функции (аргументы) RETURNS возвращаемый_тип
AS $$
  # Тело функции на PL/Python
$$ LANGUAGE plpython3u;

Тело функции содержит просто скрипт на языке Python. Когда вызывается функция, её аргументы передаются в виде элементов списка args. Результат из кода Python возвращается обычным способом, командой return или yield (в случае функции, возвращающей множество). Если возвращаемое значение не определено, Python возвращает None. Исполнитель PL/Python преобразует None языка Python в значение NULL языка SQL.

Например, функцию, возвращающее большее из двух целых чисел, можно определить так:

CREATE FUNCTION pymax (a int, b int) RETURNS int
AS $$
  if a > b:
    return a
  return b
$$ LANGUAGE plpython3u;

Проблема переопределения аргументов функций

Значения аргументов задаются в глобальных переменных. Согласно правилам видимости в Python, тонким следствием этого является то, что переменной аргумента нельзя присвоить внутри функции выражение, включающее имя самой этой переменной, если только эта переменная не объявлена глобальной в текущем блоке. Например, следующий код не будет работать:

CREATE FUNCTION pystrip(x text) RETURNS text
AS $$
  x = x.strip()  # ошибка
  return x
$$ LANGUAGE plpython3u;

Добавив оператор global, это можно исправить:

CREATE FUNCTION pystrip(x text) RETURNS text
AS $$
  global x
  x = x.strip()  # теперь всё в порядке
  return x
$$ LANGUAGE plpython3u;

Анонимные функции

PL/Python также поддерживает анонимные блоки кода, которые выполняются оператором DO:

DO $$
    # Код на PL/Python
$$ LANGUAGE plpython3u;

3. Процедуры

Процедура — объект базы данных, подобный функции, но не возвращающий значение. Она обладает следующими особенностями по сравнению с функциями:

  • Процедура определяется командой CREATE PROCEDURE.
  • В CREATE PROCEDURE отсутствует команда RETURNS. Однако процедуры могут выдавать данные в вызывающий код через выходные параметры.
  • Вызывается отдельно командой CALL.
  • Процедура может фиксировать или откатывать транзакции во время её выполнения (а затем автоматически начинать новую транзакцию), если вызывающая команда CALL находится не в блоке транзакции.
  • Некоторые модификаторы функций (например, STRICT) неприменимы к процедурам. Эти модификаторы влияют на вызов функций в запросах и не имеют отношения к процедурам.

Пример процедуры вставки записей в таблицу:

CREATE OR REPLACE PROCEDURE insert_acc_record(account_id int, balance numeric) AS $$
    INSERT INTO bank VALUES (account_id, balance);
$$ LANGUAGE SQL;

CALL insert_acc_record(1, 5.0);
CALL insert_acc_record(2, 0.0);

Пример процедуры удаления записей из таблицы:

CREATE OR REPLACE PROCEDURE clean_negative_balance() AS $$
   DELETE FROM bank
    WHERE balance < 0;
$$ LANGUAGE SQL;

CALL clean_negative_balance();

Процедуры с выходными параметрами

Процедуры также поддерживают выходные параметры, но несколько иначе, чем функции. В командах CALL выходные параметры должны быть включены в список аргументов. Обычно принято писать на их месте NULL. Например, описанную ранее операцию списания средств с банковского счета можно записать следующим образом:

CREATE OR REPLACE PROCEDURE debit_bank_acc(account_id int, debit numeric, OUT new_balance numeric) AS $$
   UPDATE bank
    SET balance = balance - debit
    WHERE account_id = debit_bank_acc.account_id
   RETURNING balance;
$$ LANGUAGE SQL;

CALL debit_bank_acc(1, 3, NULL);

4. Триггеры

Триггер — хранимая процедура особого типа, которую пользователь не вызывает непосредственно, а она сама исполняется при по модификации данных:

  • Добавлением INSERT;
  • Удалением DELETE строки в заданной таблице;
  • Изменением UPDATE данных в определённом столбце заданной таблицы реляционной базы данных.

Триггеры могут выполняться до/после/вместо основного действия и для всей строки/всего выражения модификации данных:

Алгоритм создания триггера:

  1. Создание специальной хранимой функции, которая:
    • Не принимает ничего на вход;
    • Возвращает тип TRIGGER;
    • Может использовать специальные переменные вида TG_variable.
  2. Создание непосредственно триггера, который запускает эту функцию из п.1.

Синтаксис создания триггера:

CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER имя { BEFORE | AFTER | INSTEAD OF } { событие [ OR ... ] }
    ON имя_таблицы
    [ FROM ссылающаяся_таблица ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] имя_переходного_отношения } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( условие ) ]
EXECUTE { FUNCTION | PROCEDURE } имя_функции ( аргументы )

-- Здесь допускаются события:
   INSERT
   UPDATE [ OF имя_столбца [, ... ] ]
   DELETE
   TRUNCATE

Специальные переменные вида TG_variable:

  • NEW – переменная типа RECORD, содержащая новую строку UPDATE / INSERT операций. NULL для DELETE;
  • OLD – переменная типа RECORD, содержащая старую строку UPDATE / DELETE операций. NULL для INSERT;
  • TG_WHEN – переменная типа TEXT, указывающая на время срабатывания триггера: BEFORE, AFTER, INSTEAD OF;
  • TG_LEVEL – переменная типа TEXT, соответствующая типу триггера по уровню срабатывания: ROW, STATEMENT;
  • TG_OP – переменная типа TEXT, соответствующая типу операции, на которую был вызван триггер: INSERT, DELETE, UPDATE, TRUNCATE;
  • TG_TABLE_NAME – переменная типа NAME, соответствующая таблице, которая вызвала срабатывание триггера;
  • TG_TABLE_SCHEMA – переменная типа NAME, соответствующая схеме, в которой хранится таблица, вызвавшая срабатывание триггера.

Рассмотрим пример триггера, логгирующий изменения таблицы accounts в таблицу account_changes:

CREATE TABLE accounts (
    id serial PRIMARY KEY,
    username varchar(50) NOT NULL,
    balance numeric(10, 2) DEFAULT 0
);

-- Таблица журнала изменений
CREATE TABLE account_changes (
     id serial PRIMARY KEY,
     account_id int NOT NULL,
     changed_at timestamp DEFAULT current_timestamp,
     old_data jsonb,
     new_data jsonb
);

-- Определение функции log_account_update()
CREATE OR REPLACE FUNCTION log_account_update() RETURNS TRIGGER AS $$
   BEGIN
      INSERT INTO account_changes (account_id, old_data, new_data)
      VALUES (NEW.id, to_jsonb(OLD), to_jsonb(NEW));
      RETURN NULL;
   END;
$$ LANGUAGE plpgsql;

-- Создание триггера на таблицу accounts
CREATE OR REPLACE TRIGGER log_update
    AFTER UPDATE ON accounts
    FOR EACH ROW
    WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE FUNCTION log_account_update();
INSERT INTO accounts (username, balance)
VALUES
   ('bushido_zho', 100.00),
   ('pozdnyakov', 200.00);

-- Здесь сработает триггер
UPDATE accounts
SET balance = 150.00
WHERE username = 'bushido_zho';

Триггеры создаются вне схем, в привязке к конкретной таблице базы данных. Удаляются тоже с указанием таблицы, на которую триггер создавался:

DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]

5. Полезные ссылки


6. Практическое задание

Функции, процедуры:

  1. Требуется написать функцию, которая будет выводить на экран фразу «Hello, World!»;
  2. Требуется написать функцию, которая будет переворачивать строку, получаемую на вход;
  3. Требуется написать функцию, которая будет рассчитывать факториал заданного числа;
  4. Требуется написать функцию, которая будет прибавлять к дате в формате YYYY-MM-DD n дней;
  5. Требуется написать код, который создаст копии всех имеющихся таблиц вашей БД какой-либо схемы, например, добавив к ним суффикс copy;
  6. Требуется написать функцию на PL/pgSQL, которая проверяет по возрасту пользователя, является ли он совершеннолетним;
  7. Требуется написать функцию на PL/pgSQL, которая возвращает возраст пользователя по его имени, если пользователь есть в таблице, NULL в противном случае;
  8. Требуется написать функцию, которая считает сумму чисел от 1 до n;
  9. Требуется написать функцию на PL/Python, которая возводит заданное число в указанную степень;
  10. Требуется написать функцию на PL/Python, которая проверяет является ли заданная строка палиндромом (читается одинаково слева направо и справа налево);
  11. Требуется написать функцию на PL/Python, которая считает количество слов в строке;
  12. Требуется написать процедуру, которая удаляет из таблицы всех пользователей, старше заданного значения;
  13. Требуется написать процедуру, которая подсчитывает общее количество пользователей в таблице и возвращает результат;
  14. Требуется написать процедуру, которая добавляет пользователя в таблицу по имени и дате регистрации. Если дата не передана, использовать текущую дату.

Триггеры:

Пусть дана таблица employee, в которой содержится информация о сотрудниках и их зарплате, а также информация о том, кто и когда в последний раз менял запись (создайте и наполните ее самостоятельно):

  1. Требуется создать триггер, который при любом добавлении или изменении строки в таблице сохраняет в этой строке информацию о текущем пользователе и отметку времени. Кроме того, он требует, чтобы было указано имя сотрудника и зарплата задавалась положительным числом.
  2. Требуется создать триггер, который будет записывать все изменения таблицы employee в отдельную таблицу логов. Информация, которая должна быть отражена в таблице логов:
    • Какая операция была совершена;
    • Время операции;
    • Пользователь, который совершил операцию;
    • Значения новых полей.
  3. Требуется добавить к таблице employee поле project_id и создать на нее триггер, который будет автоматически обновлять статус проекта в таблице projects. Если к проекту добавлен хотя бы один сотрудник, статус проекта меняется на “Active”. Если все сотрудники удалены из проекта, статус проекта меняется на “Pending”.
  4. Требуется создать триггер, который записывает в таблицу логов все добавления или удаления сотрудников из проектов. В логах должны храниться:
    • Время операции;
    • Тип операции (“ADD” или “REMOVE”);
    • Сотрудник, который добавлен или удален;
    • Проект, в котором произошли изменения.