JavaRush /Курсы /SQL SELF /Вложенные вызовы процедур с EXECUTE: динамическое выполне...

Вложенные вызовы процедур с EXECUTE: динамическое выполнение SQL-кода

SQL SELF
53 уровень , 4 лекция
Открыта

Прежде чем мы перейдем к практике, давайте ответим на вопрос: что же такое динамический SQL? Представьте, что вам нужно создать таблицу с уникальным именем, которое передается в качестве параметра. Или выполнить запрос к таблице, имя которой определяется на стадии выполнения программы. Здесь простого статического SQL недостаточно — вот где приходит на помощь динамическое выполнение.

PL/pgSQL предоставляет команду EXECUTE, которая выполняет SQL-запрос, переданный в виде строки. Это дает вам возможность строить и запускать SQL-код "на лету", создавая запросы, которые отличаются в зависимости от параметров.

Причины, по которым динамический SQL может быть полезным:

  1. Гибкость: Возможность строить запросы динамически в зависимости от входных данных. Например, выполнение операций над таблицами или колонками, имена которых неизвестны заранее.
  2. Автоматизация: Создание таблиц или индексов с уникальными именами.
  3. Универсальность: Возможность работы с разной структурой данных без необходимости переписывать процедуру.

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

Синтаксис EXECUTE

Использование динамического SQL через EXECUTE выглядит следующим образом:

EXECUTE 'SQL-строка';

Пример простого запроса:

DO $$
BEGIN
  EXECUTE 'CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)';
END $$;

Этот блок кода создаст таблицу test_table. Всё просто, но давайте разберемся с более сложными сценариями.

Примеры использования EXECUTE

1. Создание таблицы с динамическим именем

Предположим, у вас есть задача создавать таблицы с именами, зависящими от текущей даты. Вот как это реализовать:

DO $$
DECLARE
  table_name TEXT;
BEGIN
  -- Генерируем имя таблицы
  table_name := 'report_' || to_char(CURRENT_DATE, 'YYYYMMDD');

  -- Создаем таблицу с динамическим именем
  EXECUTE 'CREATE TABLE ' || table_name || ' (id SERIAL PRIMARY KEY, data TEXT)';

  -- Выводим сообщение для проверки
  RAISE NOTICE 'Таблица % успешно создана', table_name;
END $$;

Здесь динамическое имя генерируется из текущей даты, а итоговая SQL-строка передается в EXECUTE.

2. Выполнение запроса с динамическими параметрами

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

CREATE OR REPLACE FUNCTION get_data_from_table(table_name TEXT)
RETURNS TABLE(id INTEGER, name TEXT) AS $$
BEGIN
  RETURN QUERY EXECUTE
    'SELECT id, name FROM ' || table_name || ' WHERE id < 10';
END $$ LANGUAGE plpgsql;

Вызов функции:

SELECT * FROM get_data_from_table('employees');

Этот подход отлично подходит для построения универсальных утилит, таких как динамические отчетные системы.

Проблемы и ограничения динамического SQL

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

  1. SQL-инъекции: если вы передаете строковые параметры в запрос без обработки, вы можете дать злоумышленнику возможность выполнить произвольный SQL-код.

    Пример уязвимого кода:

    EXECUTE 'SELECT * FROM users WHERE name = ''' || user_input || '''';
    

    Если user_input содержит строку '; DROP TABLE users; --, то запрос уничтожит таблицу users.

  2. Сложность отладки: динамический код сложнее анализировать и отлаживать, так как запрос строится и выполняется во время исполнения.

  3. Потеря производительности: динамические запросы перекрывают механизмы кэширования плана выполнения в PostgreSQL, что может привести к снижению производительности.

Как защититься от SQL-инъекций

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

Пример безопасного кода:

DO $$
DECLARE
  table_name TEXT;
  user_input TEXT := 'John';
BEGIN
  table_name := 'employees';

  EXECUTE 'SELECT * FROM ' || quote_ident(table_name) ||
          ' WHERE name = ' || quote_literal(user_input);
END $$;

Реализация: динамическое обновление таблиц

Вот пример процедуры, которая обновляет значения в таблице с именем, переданным в качестве параметра:

CREATE OR REPLACE FUNCTION update_table_data(table_name TEXT, id_value INT, new_data TEXT)
RETURNS VOID AS $$
BEGIN
  EXECUTE 'UPDATE ' || quote_ident(table_name) ||
          ' SET data = ' || quote_literal(new_data) ||
          ' WHERE id = ' || id_value;
END $$ LANGUAGE plpgsql;

Вызов функции:

SELECT update_table_data('test_table', 1, 'Updated Value');

Пример: создание отчета для клиента

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

CREATE OR REPLACE FUNCTION create_client_report(client_id INT)
RETURNS VOID AS $$
DECLARE
  table_name TEXT;
BEGIN
  -- Формируем имя таблицы отчета
  table_name := 'client_report_' || client_id;

  -- Создаем таблицу для отчета
  EXECUTE 'CREATE TABLE ' || quote_ident(table_name) || ' (order_id INT, amount NUMERIC)';

  -- Заполняем таблицу данными
  EXECUTE 'INSERT INTO ' || quote_ident(table_name) ||
          ' SELECT order_id, amount FROM orders WHERE client_id = ' || client_id;

  RAISE NOTICE 'Отчет для клиента % создан: таблица %', client_id, table_name;
END $$ LANGUAGE plpgsql;

Динамический SQL с EXECUTE — это мощный инструмент, который открывает невероятные возможности для автоматизации и гибкости в PL/pgSQL. Используйте его с осторожностью, помня о рисках SQL-инъекций. Если вы хотите, чтобы ваши запросы были надежными и безопасными, применяйте функции quote_ident() и quote_literal().

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

2
Задача
SQL SELF, 53 уровень, 4 лекция
Недоступна
Создание таблицы с динамическим именем
Создание таблицы с динамическим именем
1
Опрос
Вложенные транзакции, 53 уровень, 4 лекция
Недоступен
Вложенные транзакции
Вложенные транзакции
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
ТимБай Уровень 61
17 июля 2026
Тут есть важный нюанс: для защиты от SQL‑инъекций в EXECUTE лучше не полагаться на quote_literal() и quote_ident(), а использовать параметры через USING. Почему quote_literal() — не лучший выбор quote_literal() просто оборачивает строку в кавычки и экранирует спецсимволы. Это выглядит как защита, но: Это не замена параметрам: логика подстановки остаётся на уровне конкатенации, а значит, выше риск ошибиться. В сложных случаях (например, при работе с типами jsonb, bytea, спецкодировками) ручное экранирование может вести себя неочевидно. Код становится менее читаемым и труднее тестируется. Учитывая твой фокус на надёжности и обработке ошибок в PL/pgSQL, такой подход добавляет лишний слой, где легко допустить баг. Что действительно надёжно: EXECUTE ... USING EXECUTE 'SELECT * FROM users WHERE name = $1' USING user_input; Здесь PostgreSQL сам отвечает за корректную передачу значения, независимо от содержимого user_input. Даже если там '; DROP TABLE users; --', это будет просто строка в условии WHERE. Где уместны quote_ident() и format() Их правильно применять только для идентификаторов (имена таблиц, колонок), которые действительно должны быть динамическими. Потому что при использовании EXECUTE ... USING PostgreSQL не подставляет значение в текст запроса, а передаёт его отдельно как параметр. Как это работает «под капотом» Когда ты пишешь: EXECUTE 'SELECT * FROM users WHERE name = $1' USING user_input; происходит следующее:
ТимБай Уровень 61
17 июля 2026
Текст запроса фиксируется как есть: PostgreSQL видит шаблон SELECT * FROM users WHERE name = $1. В нём нет никакой конкатенации, никаких кавычек, которые ты мог бы «сломать». Значение user_input передаётся отдельно (на уровне протокола клиент–сервер, либо как отдельный параметр внутри движка). Сервер интерпретирует $1 как плейсхолдер и подставляет туда значение уже в момент выполнения, строго в рамках типа данных и правил экранирования, которые контролирует сам PostgreSQL. Поэтому строка '; DROP TABLE users; --' не становится частью SQL‑кода: для базы это просто содержимое поля name, а не команды. В итоге реально выполняется что-то вроде «найти пользователя с именем '; DROP TABLE users; --'» — и ничего больше.
Анатолий Уровень 63
16 марта 2026
❤️
Глеб Уровень 61
24 февраля 2026
То ли я ослеп, то ли в курсе нигде не было лекции про функцию format() и про экранирование кавычек...