В программировании важны две вещи: понимать, что происходит (особенно когда все идет не так, как вы задумали), и возвращать полезные данные. Это особенно актуально для PL/pgSQL, ведь работа происходит на стороне сервера, а значит, дебаг — это не всегда просто. На помощь приходят встроенные инструменты:
RAISE NOTICE — это способ выводить сообщения во время выполнения функции. Представьте его как console.log в JavaScript или print в Python. Вы можете показать значения переменных, текущее состояние выполнения или просто оставить "приветствие" для будущего себя.
RETURN QUERY — это способ вернуть набор данных, например целую таблицу или результат сложного запроса. С его помощью PL/pgSQL функции становятся похожи на полноценные SQL-запросы.
Команда RAISE NOTICE
RAISE NOTICE позволяет выводить сообщения на экран в процессе выполнения функции. Формат выглядит так:
RAISE NOTICE 'Сообщение: %', значение;
%— это плейсхолдер для переменной, похожий наprintfв C.- После текста сообщения можно перечислить переменные, которые нужно подставить.
Пример использования
Представьте, что вы пишете функцию, которая подсчитывает количество студентов в разных группах. Вы хотите видеть промежуточные значения, чтобы понять, всё ли идет по плану.
CREATE OR REPLACE FUNCTION count_students_in_groups() RETURNS VOID AS $$
DECLARE
group_name TEXT;
student_count INT;
BEGIN
FOR group_name IN SELECT DISTINCT group_name FROM students LOOP
SELECT COUNT(*) INTO student_count
FROM students WHERE group_name = group_name;
-- Вывод результатов
RAISE NOTICE 'Группа: %, Количество студентов: %', group_name, student_count;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Когда вы вызовете эту функцию:
SELECT count_students_in_groups();
Вы увидите подобные сообщения в логах:
NOTICE: Группа: Математика, Количество студентов: 30
NOTICE: Группа: Философия, Количество студентов: 25
NOTICE: Группа: Биология, Количество студентов: 18
Заметьте, что функция ничего не возвращает (она создаётся с RETURNS VOID), но RAISE NOTICE показывает нам, как продвигается цикл.
Полезные трюки с RAISE
Кроме NOTICE, вы можете использовать и другие уровни сообщений:
RAISE DEBUG— для дополнительной информации (отображается, только если установлен уровень логирования DEBUG).RAISE INFO— для общей информации.RAISE WARNING— для предупреждений.RAISE EXCEPTION— для генерации ошибок, что мы изучим позже.
Для отладки лучше использовать NOTICE или DEBUG, так как они удобны и не останавливают выполнение функции.
Команда RETURN QUERY: верните данные, как профи
RETURN QUERY используется в PL/pgSQL для возврата набора строк. С его помощью мы можем вернуть результат SQL-запроса прямо из функции. Синтаксис следующий:
RETURN QUERY <SQL-запрос>;
Также можно комбинировать несколько запросов:
RETURN QUERY <SQL-запрос 1>;
RETURN QUERY <SQL-запрос 2>;
Пример 1: функция с RETURN QUERY
Напишем функцию, которая возвращает список студентов из заданной группы.
CREATE OR REPLACE FUNCTION get_students_by_group(group_name TEXT)
RETURNS TABLE(id INT, name TEXT) AS $$
BEGIN
RETURN QUERY
SELECT id, name
FROM students
WHERE group_name = group_name;
END;
$$ LANGUAGE plpgsql;
Теперь вызовем эту функцию:
SELECT * FROM get_students_by_group('Математика');
Чтобы проверить работу функции, сначала создадим таблицу students и добавим в неё данные:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
group_name TEXT NOT NULL
);
INSERT INTO students (name, group_name) VALUES
('Otto Song', 'Физика'),
('Alex Lin', 'Математика'),
('Anna Vel', 'Математика'),
('Maria Chi', 'История');
Результат:
| id | name |
|---|---|
| 2 | Alex Lin |
| 3 | Anna Vel |
Как видите, функция работает как обычный SQL-запрос.
Пример 2: Совмещение нескольких запросов
Что если нам нужно вернуть объединённые данные из нескольких таблиц? Давайте вернем список студентов и курсов, на которые они записаны.
CREATE OR REPLACE FUNCTION get_students_and_courses()
RETURNS TABLE(student_name TEXT, course_name TEXT) AS $$
BEGIN
RETURN QUERY
SELECT s.name, c.name
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON e.course_id = c.id;
END;
$$ LANGUAGE plpgsql;
Для начала создадим три таблицы: students, courses и enrollments, а затем добавим немного данных:
-- Таблица студентов
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
-- Таблица курсов
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
-- Таблица записей на курсы (связующая)
CREATE TABLE enrollments (
student_id INT REFERENCES students(id),
course_id INT REFERENCES courses(id)
);
-- Добавим студентов
INSERT INTO students (name) VALUES
('Otto Song'),
('Alex Lin'),
('Anna Vel'),
('Maria Chi');
-- Добавим курсы
INSERT INTO courses (name) VALUES
('Математика'),
('Физика'),
('История');
-- Добавим записи на курсы
INSERT INTO enrollments (student_id, course_id) VALUES
(1, 2), -- Otto -> Физика
(2, 1), -- Alex -> Математика
(3, 1), -- Anna -> Математика
(4, 3); -- Maria -> История
В этом случае вызов функции:
SELECT * FROM get_students_and_courses();
даст следующий результат:
| student_name | course_name |
|---|---|
| Otto Song | Физика |
| Alex Lin | Математика |
| Anna Vel | Математика |
| Maria Chi | История |
Функция аккуратно объединяет данные из трёх таблиц и показывает, кто из студентов на какой курс записан.
Совмещение RAISE NOTICE и RETURN QUERY
Иногда RETURN QUERY и RAISE NOTICE могут работать в одной функции, чтобы вы могли контролировать выполнение функции и видеть промежуточные результаты.
Вот пример функции, которая возвращает данные о студентах и одновременно выводит сообщения, чтобы показать ход выполнения:
CREATE OR REPLACE FUNCTION debug_students()
RETURNS TABLE(student_id INT, student_name TEXT) AS $$
DECLARE
count_students INT;
BEGIN
-- Подсчитываем количество студентов
SELECT COUNT(*) INTO count_students FROM students;
RAISE NOTICE 'Всего студентов: %', count_students;
-- Возвращаем данные о студентах
RETURN QUERY
SELECT id, name FROM students;
RAISE NOTICE 'Функция завершила выполнение.';
END;
$$ LANGUAGE plpgsql;
Если таблица students ещё не создана, добавьте её и внесите данные:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
INSERT INTO students (name) VALUES
('Otto Song'),
('Alex Lin'),
('Anna Vel'),
('Maria Chi');
Теперь, вызвав функцию:
SELECT * FROM debug_students();
Вы получите в выводе как данные, так и сообщения:
| student_id | student_name |
|---|---|
| 1 | Otto Song |
| 2 | Alex Lin |
| 3 | Anna Vel |
| 4 | Maria Chi |
Вывод сообщения в консоль:
NOTICE: Всего студентов: 4
NOTICE: Функция завершила выполнение.
Типичные ошибки при использовании
Ошибка с переменными в RAISE NOTICE: если вы забыли объявить переменную или допустили ошибку в её имени, вы получите ошибку variable does not exist. Всегда проверяйте, что переменные объявлены корректно.
Ошибка с типом возврата: если вы используете RETURN QUERY, но не указываете RETURNS TABLE при создании функции, PostgreSQL выдаст ошибку. Убедитесь, что типы возврата совпадают с возвращаемыми данными.
Ошибка с плейсхолдерами в RAISE: если количество плейсхолдеров % не соответствует количеству переменных, появится ошибка. Например:
RAISE NOTICE 'Значение: %, %', value1;
Это вызовет ошибку из-за отсутствия второй переменной.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ