Хранимые процедуры
Хранимые процедуры — программы, живущие внутри базы данных (SQL, условия, циклы, переменные, обработка ошибок). Преимущества: производительность (скомпилированный план), снижение сетевого трафика, инкапсуляция логики, безопасность (привилегии через процедуру), уменьшение дублирования кода. Недостатки: привязка к конкретной СУБД (PL/pgSQL, T-SQL, PL/SQL), сложность отладки и версионирования, разделение логики. Параметры: IN (входной), OUT (выходной), INOUT. Управляющие конструкции (IF, CASE, LOOP, WHILE), обработка ошибок (EXCEPTION, TRY/CATCH). Отличие от пользовательских функций (функции возвращают значение и могут быть в SELECT, процедуры изменяют данные и управляют транзакциями). Модели использования (толстая/тонкая процедура), безопасность (SQL-инъекции, динамический SQL), практические рекомендации (когда использовать, когда избегать).
Введение: Программа внутри базы данных
Представьте, что вы каждый день готовите сложный отчет для руководства. Вы пишете один и тот же запрос, потом другой, потом третий, объединяете результаты, перепроверяете данные. Это занимает час. А если бы вы могли сохранить всю последовательность действий под одним именем и просто вызывать ее — “сформировать отчет”?
Именно так и работают хранимые процедуры. Это программы, которые живут и выполняются прямо внутри базы данных. Вы пишете код один раз, сохраняете его в базе данных, а потом можете вызывать снова и снова, передавая разные параметры.
Хранимая процедура (stored procedure) — это набор SQL-команд и логики управления (условия, циклы, переменные, обработка ошибок), который сохраняется на сервере базы данных и может выполняться по требованию.
В отличие от обычного SQL-запроса, который отправляется из приложения и выполняется один раз, хранимая процедура:
- Хранится на сервере (не нужно передавать длинный текст запроса по сети).
- Может содержать сложную логику (условия, циклы, временные таблицы).
- Может принимать параметры и возвращать результаты.
- Может вызывать другие хранимые процедуры.
Зачем нужны хранимые процедуры
Плюсы использования
Производительность:
Хранимая процедура компилируется один раз (при создании или при первом выполнении), и план выполнения сохраняется. При последующих вызовах база данных не тратит время на разбор и оптимизацию запроса. Для сложных операций, выполняемых многократно, выигрыш может быть значительным.
Снижение сетевого трафика:
Вместо того чтобы отправлять на сервер десять длинных SQL-запросов, приложение отправляет одно короткое сообщение: “выполни процедуру X с параметрами Y”. Это особенно важно, когда приложение и база данных находятся на разных серверах.
Инкапсуляция логики:
Хранимая процедура скрывает детали реализации. Приложение знает только: “вызови процедуру перевода денег с параметрами (откуда, куда, сумма)”. Как именно происходит перевод, проверка баланса, запись в журнал — это детали, скрытые внутри процедуры. Если логика изменится, процедуру можно изменить в одном месте, не трогая приложение.
Безопасность:
Можно дать пользователю право выполнять хранимую процедуру, но не давать прямого доступа к таблицам. Пользователь сможет, например, перевести деньги через процедуру, но не сможет напрямую изменить баланс или прочитать чужие данные. Это называется “привилегии через процедуру”.
Уменьшение дублирования кода:
Если одна и та же бизнес-операция нужна в разных местах (веб-интерфейс, мобильное приложение, API для партнеров), ее логика пишется один раз в хранимой процедуре. Все приложения вызывают одну и ту же процедуру, гарантируя одинаковое поведение.
Минусы и ограничения
| Минус | Описание |
|---|---|
| Привязка к конкретной СУБД | Хранимые процедуры пишутся на языке, специфичном для вашей базы данных (PL/pgSQL в PostgreSQL, T-SQL в SQL Server, PL/SQL в Oracle). Перенос на другую СУБД потребует переписывания |
| Сложность отладки | Отлаживать хранимые процедуры сложнее, чем код на Python, Java или C#. Инструменты отладки есть, но они менее удобны |
| Сложность версионирования | Хранимые процедуры хранятся в базе данных, а не в системе контроля версий (если не настроено специальное решение). Изменение процедуры напрямую в продакшене — рискованно |
| Разделение логики | Логика оказывается разделенной между приложением и базой данных. Новому разработчику сложнее понять, где что находится |
| Масштабирование | База данных — это единая точка. Если логика сильно нагружает процессор, вы не можете “добавить еще один сервер базы данных” так же легко, как добавить сервер приложения |
Синтаксис и базовая структура
Синтаксис хранимых процедур сильно различается в разных СУБД. Ниже приведены примеры для самых популярных.
PostgreSQL (PL/pgSQL)
-- Создание процедуры
CREATE OR REPLACE PROCEDURE transfer_money(
p_from_account INT,
p_to_account INT,
p_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
DECLARE
v_balance DECIMAL;
BEGIN
-- Проверка остатка на счете отправителя
SELECT balance INTO v_balance
FROM accounts
WHERE id = p_from_account
FOR UPDATE;
IF v_balance < p_amount THEN
RAISE EXCEPTION 'Недостаточно средств';
END IF;
-- Списание и зачисление
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
-- Запись в журнал
INSERT INTO transactions (from_account, to_account, amount, created_at)
VALUES (p_from_account, p_to_account, p_amount, NOW());
COMMIT;
END;
$$;
-- Вызов процедуры
CALL transfer_money(1, 2, 100.00);SQL Server (T-SQL)
-- Создание процедуры
CREATE PROCEDURE transfer_money
@from_account INT,
@to_account INT,
@amount DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @balance DECIMAL(10,2);
BEGIN TRANSACTION;
-- Проверка остатка
SELECT @balance = balance
FROM accounts WITH (UPDLOCK)
WHERE id = @from_account;
IF @balance < @amount
BEGIN
ROLLBACK;
RAISERROR('Недостаточно средств', 16, 1);
RETURN;
END;
-- Списание и зачисление
UPDATE accounts SET balance = balance - @amount WHERE id = @from_account;
UPDATE accounts SET balance = balance + @amount WHERE id = @to_account;
-- Запись в журнал
INSERT INTO transactions (from_account, to_account, amount, created_at)
VALUES (@from_account, @to_account, @amount, GETDATE());
COMMIT TRANSACTION;
END;
-- Вызов процедуры
EXEC transfer_money @from_account = 1, @to_account = 2, @amount = 100.00;MySQL
-- Создание процедуры
DELIMITER //
CREATE PROCEDURE transfer_money(
IN p_from_account INT,
IN p_to_account INT,
IN p_amount DECIMAL(10,2)
)
BEGIN
DECLARE v_balance DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- Проверка остатка
SELECT balance INTO v_balance
FROM accounts
WHERE id = p_from_account
FOR UPDATE;
IF v_balance < p_amount THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Недостаточно средств';
END IF;
-- Списание и зачисление
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
-- Запись в журнал
INSERT INTO transactions (from_account, to_account, amount, created_at)
VALUES (p_from_account, p_to_account, p_amount, NOW());
COMMIT;
END//
DELIMITER ;
-- Вызов процедуры
CALL transfer_money(1, 2, 100.00);Oracle (PL/SQL)
-- Oracle различает процедуры и функции
CREATE OR REPLACE PROCEDURE transfer_money(
p_from_account IN accounts.id%TYPE,
p_to_account IN accounts.id%TYPE,
p_amount IN accounts.balance%TYPE
) AS
v_balance accounts.balance%TYPE;
BEGIN
-- Проверка остатка (с блокировкой строки)
SELECT balance INTO v_balance
FROM accounts
WHERE id = p_from_account
FOR UPDATE;
IF v_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20001, 'Недостаточно средств');
END IF;
-- Списание и зачисление
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
-- Запись в журнал
INSERT INTO transactions (from_account, to_account, amount, created_at)
VALUES (p_from_account, p_to_account, p_amount, SYSDATE);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END transfer_money;
/
-- Вызов процедуры (в анонимном блоке)
BEGIN
transfer_money(1, 2, 100.00);
END;
/Параметры хранимых процедур
Хранимые процедуры могут принимать параметры и возвращать значения. В разных СУБД типы параметров различаются.
Типы параметров
| Тип | Направление | Описание |
|---|---|---|
| IN | Входной | Значение передается в процедуру. Процедура не может его изменить |
| OUT | Выходной | Процедура возвращает значение через этот параметр |
| INOUT | Входной и выходной | Значение передается в процедуру, процедура может его изменить и вернуть новое |
Пример с OUT-параметрами (PostgreSQL):
CREATE OR REPLACE PROCEDURE get_user_stats(
p_user_id INT,
OUT p_order_count INT,
OUT p_total_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT COUNT(*), SUM(amount)
INTO p_order_count, p_total_amount
FROM orders
WHERE user_id = p_user_id;
END;
$$;
-- Вызов с OUT-параметрами
CALL get_user_stats(1, NULL, NULL); -- NULL на месте OUT-параметровПример с INOUT-параметром (SQL Server):
CREATE PROCEDURE add_tax
@amount DECIMAL(10,2) INOUT,
@tax_rate DECIMAL(5,2)
AS
BEGIN
SET @amount = @amount * (1 + @tax_rate / 100);
END;
-- Использование
DECLARE @price DECIMAL(10,2) = 100.00;
EXEC add_tax @amount = @price INOUT, @tax_rate = 20;
SELECT @price; -- 120.00Параметры со значениями по умолчанию
-- PostgreSQL
CREATE OR REPLACE PROCEDURE search_products(
p_category VARCHAR DEFAULT NULL,
p_min_price DECIMAL DEFAULT 0,
p_max_price DECIMAL DEFAULT 999999
)
LANGUAGE plpgsql
AS $$
BEGIN
-- тело процедуры
END;
$$;
-- Вызов с использованием значений по умолчанию
CALL search_products(); -- все параметры по умолчанию
CALL search_products(p_category => 'books'); -- только категорияУправляющие конструкции
Хранимые процедуры поддерживают полноценные языки программирования с условиями, циклами и переменными.
Переменные
-- PostgreSQL
DECLARE
v_counter INT := 0;
v_name VARCHAR(100);
v_balance DECIMAL DEFAULT 0.00;
v_is_active BOOLEAN;Условные операторы
-- PostgreSQL
IF v_balance < 0 THEN
RAISE EXCEPTION 'Отрицательный баланс';
ELSIF v_balance = 0 THEN
v_status := 'empty';
ELSE
v_status := 'positive';
END IF;
-- SQL Server (CASE выражение и IF)
IF @balance < 0
BEGIN
RAISERROR('Отрицательный баланс', 16, 1);
RETURN;
END
ELSE IF @balance = 0
SET @status = 'empty';
ELSE
SET @status = 'positive';
-- MySQL
IF v_balance < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Отрицательный баланс';
ELSEIF v_balance = 0 THEN
SET v_status = 'empty';
ELSE
SET v_status = 'positive';
END IF;
-- CASE в любых СУБД (для вычисления значения)
v_status := CASE
WHEN v_balance < 0 THEN 'negative'
WHEN v_balance = 0 THEN 'empty'
ELSE 'positive'
END;Циклы
-- PostgreSQL (LOOP с выходом)
LOOP
v_counter := v_counter + 1;
EXIT WHEN v_counter > 10;
-- тело цикла
END LOOP;
-- PostgreSQL (WHILE)
WHILE v_counter <= 10 LOOP
-- тело цикла
v_counter := v_counter + 1;
END LOOP;
-- PostgreSQL (FOR по диапазону)
FOR i IN 1..10 LOOP
-- тело цикла
END LOOP;
-- PostgreSQL (FOR по результату запроса)
FOR rec IN SELECT id, name FROM users LOOP
RAISE NOTICE 'User: %, %', rec.id, rec.name;
END LOOP;
-- SQL Server
WHILE @counter <= 10
BEGIN
-- тело цикла
SET @counter = @counter + 1;
END;
-- MySQL
WHILE v_counter <= 10 DO
-- тело цикла
SET v_counter = v_counter + 1;
END WHILE;Обработка ошибок
Хранимые процедуры могут перехватывать и обрабатывать ошибки, выполнять откат транзакций и логировать проблемы.
Обработка ошибок в PostgreSQL
CREATE OR REPLACE PROCEDURE safe_transfer(
p_from INT, p_to INT, p_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
BEGIN -- вложенный блок для обработки ошибок
-- основная логика
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
EXCEPTION
WHEN check_violation THEN
RAISE NOTICE 'Ошибка проверки ограничения';
ROLLBACK;
WHEN foreign_key_violation THEN
RAISE NOTICE 'Неверный идентификатор счета';
ROLLBACK;
WHEN OTHERS THEN
RAISE NOTICE 'Неизвестная ошибка: %', SQLERRM;
ROLLBACK;
END;
END;
$$;Обработка ошибок в SQL Server
CREATE PROCEDURE safe_transfer
@from INT, @to INT, @amount DECIMAL
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - @amount WHERE id = @from;
UPDATE accounts SET balance = balance + @amount WHERE id = @to;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- Логирование ошибки
INSERT INTO error_log (error_message, error_number, error_line)
VALUES (
ERROR_MESSAGE(),
ERROR_NUMBER(),
ERROR_LINE()
);
-- Перебросить ошибку выше
THROW;
END CATCH
END;Обработка ошибок в MySQL
CREATE PROCEDURE safe_transfer(
p_from INT, p_to INT, p_amount DECIMAL
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
-- Можно логировать ошибку
RESIGNAL;
END;
START TRANSACTION;
UPDATE accounts SET balance = balance - p_amount WHERE id = p_from;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
COMMIT;
END;Хранимые процедуры vs Пользовательские функции
Это важное различие, которое нужно понимать. Пользовательские функции будут рассмотрены в отдельном документе, но здесь приведем краткое сравнение.
| Характеристика | Хранимая процедура | Пользовательская функция |
|---|---|---|
| Возвращает значение | Необязательно (OUT-параметры) | Обязательно (один результат) |
| Может быть в SELECT | Нет | Да |
| Может изменять данные | Да | Нет (кроме табличных функций) |
| Может управлять транзакциями | Да | Нет (COMMIT/ROLLBACK запрещены) |
| Может вызывать процедуры | Да | Ограниченно |
| Использование в WHERE | Нет | Да |
Когда использовать процедуру, а когда функцию:
| Сценарий | Выбор |
|---|---|
| Нужно изменить данные (INSERT, UPDATE, DELETE) | Процедура |
| Нужно управлять транзакциями (COMMIT/ROLLBACK) | Процедура |
| Нужно вернуть одно значение для использования в SELECT | Функция |
| Нужно использовать в WHERE, JOIN, HAVING | Функция |
| Нужно вернуть результирующий набор для отчета | Процедура (или табличная функция) |
| Нужно выполнить сложную бизнес-логику с несколькими шагами | Процедура |
Хранимые процедуры в архитектуре приложений
Модели использования
Толстая процедура (Fat Procedure):
Вся бизнес-логика вынесена в хранимые процедуры. Приложение — это тонкая прослойка, которая только вызывает процедуры и отображает результаты.
Плюсы: максимальная производительность (нет сетевых вызовов между шагами), централизованная логика.
Минусы: привязка к СУБД, сложность отладки и версионирования.
Тонкая процедура (Thin Procedure):
В хранимых процедурах только простые операции, часто используемые в разных местах. Основная бизнес-логика в приложении.
Плюсы: гибкость, простота разработки, независимость от СУБД.
Минусы: больше сетевых вызовов, больше кода в приложении.
Компромиссный подход:
- Критичные по производительности операции — в процедурах.
- Сложная бизнес-логика с частыми изменениями — в приложении.
- Операции, требующие строгой атомарности и изоляции — в процедурах (с транзакциями).
Практические рекомендации
Когда стоит использовать хранимые процедуры:
| Сценарий | Почему |
|---|---|
| Сложные отчеты с множеством объединений и агрегаций | Меньше данных передается по сети, процедура может использовать временные таблицы |
| Пакетная обработка данных (ETL, массовые обновления) | Можно запустить на сервере, не передавая миллионы строк в приложение |
| Операции, требующие строгой атомарности | Вся логика в одном месте, проще управлять транзакцией |
| Однотипные операции с разных клиентов | Единая логика для веб-приложения, API, мобильного приложения |
| Действия, которые должны выполняться с повышенными привилегиями | Процедура может делать то, что запрещено пользователю напрямую |
Когда стоит избегать хранимых процедур:
| Сценарий | Почему |
|---|---|
| Простая бизнес-логика (CRUD) | ORM справляется отлично, процедуры усложняют |
| Логика, которая часто меняется | Изменять процедуру сложнее, чем код приложения |
| Кроссплатформенность | Процедуры привязывают к конкретной СУБД |
| Микросервисная архитектура | Логика должна быть в сервисе, а не в общей базе |
| Слабая команда по базам данных | Разработчикам приложений проще писать на знакомом языке |
Безопасность хранимых процедур
Привилегии через процедуру
Один из мощнейших механизмов безопасности в базах данных. Вместо того чтобы давать пользователю прямой доступ к таблицам, вы даете ему право выполнять процедуру. Сама процедура выполняется с правами ее владельца (или с правами вызывающего — зависит от настройки).
-- PostgreSQL: процедура выполняется с правами владельца
ALTER PROCEDURE transfer_money OWNER TO trusted_role;
GRANT EXECUTE ON PROCEDURE transfer_money TO public;
-- Пользователи могут вызвать процедуру, но не имеют доступа к таблицам accountsSQL-инъекции и хранимые процедуры
Хранимые процедуры, правильно написанные с использованием параметров, защищены от SQL-инъекций. Динамический SQL внутри процедуры (когда вы строите запрос как строку) снова становится уязвимым.
-- Безопасно: параметризованный запрос
CREATE PROCEDURE get_user(p_user_id INT)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM users WHERE id = p_user_id;
END;
$$;
-- Опасно: динамический SQL без экранирования
CREATE PROCEDURE search_users(p_name VARCHAR)
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'SELECT * FROM users WHERE name = ''' || p_name || '''';
-- Если p_name = "'; DROP TABLE users; --" — катастрофа
END;
$$;Если динамический SQL неизбежен, используйте безопасные функции экранирования (quote_ident, quote_literal в PostgreSQL, QUOTENAME в SQL Server).
Отладка и мониторинг хранимых процедур
Вывод отладочной информации
-- PostgreSQL
RAISE NOTICE 'Значение переменной: %', v_variable;
RAISE DEBUG 'Отладочная информация';
-- SQL Server
PRINT 'Значение переменной: ' + CAST(@variable AS VARCHAR);
-- MySQL (использование переменных сессии)
SET @debug_message = CONCAT('Значение: ', v_variable);Поиск медленных процедур
В большинстве СУБД есть системные представления, показывающие статистику выполнения процедур.
-- PostgreSQL (с расширением pg_stat_statements)
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
WHERE query LIKE '%transfer_money%'
ORDER BY mean_time DESC;
-- SQL Server (DMV)
SELECT
OBJECT_NAME(object_id) AS procedure_name,
execution_count,
total_worker_time / execution_count AS avg_cpu_time
FROM sys.dm_exec_procedure_stats
ORDER BY avg_cpu_time DESC;Хранимые процедуры в разных СУБД: Особенности
PostgreSQL
- Поддерживает процедуры с
CREATE PROCEDURE(с PostgreSQL 11). Ранее использовались функции для всего. - Процедуры могут управлять транзакциями (COMMIT/ROLLBACK внутри).
- Мощный язык PL/pgSQL с поддержкой массивов, JSON, курсоров.
- Поддержка перегрузки процедур (несколько процедур с одним именем, но разными параметрами).
Особенность: В PostgreSQL процедуры не возвращают результирующий набор (для этого есть функции, возвращающие SETOF или TABLE).
SQL Server
- Процедуры могут возвращать несколько результирующих наборов.
- Поддержка курсоров, временных таблиц, табличных переменных.
- Мощная обработка ошибок через TRY/CATCH.
- Поддержка динамического SQL через
sp_executesql.
Особенность: SQL Server различает процедуры и функции более строго, чем PostgreSQL.
Oracle
- PL/SQL — очень мощный язык, близкий к Ada/Pascal.
- Пакеты (packages) — способ группировать процедуры, функции, типы.
- Автономные транзакции (
PRAGMA AUTONOMOUS_TRANSACTION) — подтранзакции, которые фиксируются независимо от родительской транзакции.
Особенность: В Oracle все хранимые объекты (процедуры, функции, пакеты) компилируются в байт-код для повышения производительности.
MySQL
- Процедуры поддерживаются, но язык менее развит, чем в PostgreSQL или Oracle.
- Ограниченная поддержка массивов (нет встроенного типа array).
- Обработка ошибок через декларативные обработчики.
Особенность: MySQL требует смены разделителя (DELIMITER) при создании процедур, чтобы тело процедуры не интерпретировалось как отдельные команды.
Распространенные ошибки при работе с хранимыми процедурами
Ошибка 1: Дублирование логики между процедурами
Одна и та же проверка баланса реализована в десяти разных процедурах. Когда правила меняются, нужно менять в десяти местах.
Как исправить: Выделять общую логику в отдельные процедуры или функции. Вызывать их из других процедур.
Ошибка 2: Слишком длинные процедуры
Процедура на 2000 строк кода. Невозможно понять, что она делает, невозможно отладить, невозможно протестировать.
Как исправить: Разбивать на маленькие процедуры (20-50 строк каждая). Каждая процедура делает одно действие.
Ошибка 3: Смешивание уровней абстракции
Процедура, которая и обновляет баланс, и форматирует отчет, и отправляет email через внешний вызов.
Как исправить: Разделять бизнес-логику, операции с данными и внешние вызовы. Внешние вызовы не должны быть внутри транзакции.
Ошибка 4: Игнорирование обработки ошибок
Процедура делает UPDATE, потом UPDATE, потом COMMIT. Если второе обновление падает, первое остается незафиксированным (или частично зафиксированным, в зависимости от СУБД).
Как исправить: Всегда использовать обработку ошибок. В случае ошибки — ROLLBACK.
Ошибка 5: Неправильное управление транзакциями
-- Плохо: COMMIT внутри цикла
CREATE PROCEDURE process_orders()
AS $$
BEGIN
FOR rec IN SELECT * FROM orders WHERE status = 'pending' LOOP
UPDATE orders SET status = 'processed' WHERE id = rec.id;
COMMIT; -- Не делайте так!
END LOOP;
END;
$$;После COMMIT транзакция завершается. Если следующий UPDATE упадет, предыдущие изменения уже зафиксированы и не будут откачены. Частичная обработка — проблемы с целостностью.
Как исправить: Либо одна транзакция на весь цикл, либо разбивать на отдельные вызовы процедуры (каждый вызов — своя транзакция).
Ошибка 6: Не использовать схему именования параметров
-- Плохо: имя параметра совпадает с именем столбца
CREATE PROCEDURE update_user(id INT, name VARCHAR)
AS $$
BEGIN
UPDATE users SET name = name WHERE id = id; -- name = name? id = id?
END;
$$;Как исправить: Использовать префиксы: p_id, p_name, in_id, v_name или явно указывать таблицу: users.name, users.id.
Резюме для системного аналитика
Хранимая процедура — это программа внутри базы данных. Она содержит SQL-команды и логику управления. Вызывается из приложения, других процедур или триггеров.
Главные преимущества: производительность (меньше сетевых вызовов, скомпилированный план), безопасность (привилегии через процедуру), инкапсуляция (скрытие деталей), переиспользование кода.
Главные недостатки: привязка к конкретной СУБД, сложность отладки, проблемы с версионированием, разделение логики между приложением и базой.
Параметры бывают IN (входные), OUT (выходные), INOUT (входные и выходные). OUT-параметры позволяют возвращать значения.
Процедуры поддерживают полноценные языки программирования: переменные, условия (IF/CASE), циклы (WHILE/FOR), обработку ошибок.
Процедуры могут управлять транзакциями (COMMIT/ROLLBACK). Это ключевое отличие от пользовательских функций.
Синтаксис сильно различается между СУБД. PL/pgSQL (PostgreSQL), T-SQL (SQL Server), PL/SQL (Oracle) — это разные языки. Перенос между СУБД требует переписывания.