Что такое хранимые функции в PostgreSQL?
Хранимые функции в PostgreSQL
Хранимая функция (stored function) — это объект базы данных, содержащий именованный блок кода, который компилируется и сохраняется на сервере. Функции можно вызывать из SQL-запросов, триггеров или других функций.
Основные характеристики
- Хранятся в системном каталоге базы данных
- Выполняются на стороне сервера, снижая сетевой трафик
- Могут быть написаны на PL/pgSQL, SQL, PL/Python, PL/V8 и других языках
- Обязательно возвращают значение (в отличие от процедур
PROCEDURE) - Поддерживают перегрузку — несколько функций с одним именем, но разными параметрами
Синтаксис создания
CREATE OR REPLACE FUNCTION имя_функции(параметры)
RETURNS тип_возврата
LANGUAGE plpgsql
AS $$
DECLARE
-- объявление переменных
BEGIN
-- тело функции
RETURN значение;
END;
$$;
Типы возвращаемых значений
Скалярные значения — функция возвращает одно значение:
CREATE OR REPLACE FUNCTION get_user_age(user_id INT)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
v_age INT;
BEGIN
SELECT age INTO v_age FROM users WHERE id = user_id;
RETURN v_age;
END;
$$;
RETURNS TABLE — функция возвращает набор строк:
CREATE OR REPLACE FUNCTION get_active_users(min_age INT)
RETURNS TABLE(id INT, name TEXT, email TEXT)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT u.id, u.name, u.email
FROM users u
WHERE u.age >= min_age AND u.is_active = TRUE;
END;
$$;
-- Вызов как обычная таблица
SELECT * FROM get_active_users(18);
Категории волатильности
PostgreSQL различает три категории, влияющие на оптимизацию:
- VOLATILE (по умолчанию) — может возвращать разные результаты при одних входных данных, не кешируется
- STABLE — в рамках одной транзакции возвращает одинаковый результат для одних параметров
- IMMUTABLE — результат зависит только от аргументов, можно использовать в индексах
CREATE FUNCTION calculate_tax(amount NUMERIC)
RETURNS NUMERIC
LANGUAGE sql
IMMUTABLE
AS $$
SELECT amount * 0.2;
$$;
Функции на чистом SQL
Для простых операций без ветвлений можно использовать язык sql:
CREATE OR REPLACE FUNCTION full_name(first_name TEXT, last_name TEXT)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE
AS $$
SELECT first_name || ' ' || last_name;
$$;
Отличие от процедур (PROCEDURE)
| Функция | Процедура |
|---|---|
| Возвращает значение | Не возвращает значение |
| Вызывается в SELECT | Вызывается через CALL |
| Нельзя управлять транзакциями | Можно использовать COMMIT/ROLLBACK |
Управление функциями
-- Просмотр исходного кода функции
\df+ имя_функции
-- Удаление функции
DROP FUNCTION IF EXISTS get_user_age(INT);
Что хочет услышать интервьюер
Кандидат понимает, что функция хранится в БД и выполняется на стороне сервера
Знает обязательное отличие функции от процедуры — функция должна возвращать значение
Знаком с языками написания функций: минимум PL/pgSQL и SQL
Понимает категории волатильности (VOLATILE/STABLE/IMMUTABLE) и их влияние на производительность
Умеет объяснить, когда использование хранимых функций оправдано (инкапсуляция логики, переиспользование, безопасность)
Пример: Скалярная функция на PL/pgSQL
CREATE OR REPLACE FUNCTION get_discount_price(
original_price NUMERIC,
discount_percent INT
)
RETURNS NUMERIC
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
v_discount NUMERIC;
BEGIN
-- Проверяем корректность процента скидки
IF discount_percent < 0 OR discount_percent > 100 THEN
RAISE EXCEPTION 'Некорректный процент скидки: %', discount_percent;
END IF;
v_discount := original_price * discount_percent / 100;
RETURN original_price - v_discount;
END;
$$;
-- Вызов функции в запросе
SELECT
product_name,
price AS original_price,
get_discount_price(price, 15) AS discounted_price
FROM products
WHERE category = 'electronics';
Пример: Функция возвращающая таблицу (RETURNS TABLE)
CREATE OR REPLACE FUNCTION get_orders_by_status(
p_status TEXT,
p_limit INT DEFAULT 10
)
RETURNS TABLE(
order_id INT,
customer TEXT,
total_amount NUMERIC,
created_at TIMESTAMPTZ
)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT
o.id,
u.full_name,
o.total_amount,
o.created_at
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = p_status
ORDER BY o.created_at DESC
LIMIT p_limit;
END;
$$;
-- Вызов: функция ведёт себя как таблица
SELECT * FROM get_orders_by_status('pending', 5);
SELECT customer, total_amount FROM get_orders_by_status('completed') WHERE total_amount > 1000;
Пример: Иммутабельная SQL-функция для использования в индексе
-- Функция нормализации телефона — результат зависит только от аргумента
CREATE OR REPLACE FUNCTION normalize_phone(phone TEXT)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE
AS $$
-- Оставляем только цифры и заменяем 8 на +7 в начале
SELECT CASE
WHEN regexp_replace(phone, '[^0-9]', '', 'g') ~ '^8'
THEN '+7' || substring(regexp_replace(phone, '[^0-9]', '', 'g') FROM 2)
ELSE '+' || regexp_replace(phone, '[^0-9]', '', 'g')
END;
$$;
-- Создание функционального индекса на основе IMMUTABLE-функции
CREATE INDEX idx_users_phone_normalized
ON users (normalize_phone(phone));
-- Запрос использует индекс
SELECT * FROM users
WHERE normalize_phone(phone) = normalize_phone('+7 (999) 123-45-67');
Типичные ошибки
Путают хранимые функции и процедуры — не знают, что функции обязаны возвращать значение, а процедуры нет
Не указывают категорию волатильности, оставляя VOLATILE там, где подошло бы IMMUTABLE — упускают оптимизации
Забывают про возможность перегрузки функций по типам параметров
Не знают о RETURNS TABLE и пытаются вернуть набор строк через REFCURSOR или другие обходные пути
Считают, что хранимые функции всегда быстрее — не учитывают накладные расходы PL/pgSQL и случаи, когда чистый SQL эффективнее


