В этом руководстве ты научишься использовать хранимые процедуры, чтобы дать имя твоим SELECT запросам и сделать их переиспользуемыми.
Когда ты работаешь с любой SQL базой данных, ты можешь заметить, что одни и те же SELECT запросы используются очень часто. Они бывают короткими, тогда их несложно писать с нуля.
Но бывает так, что в одном запросе есть несколько джойнов, агрегаций, сортировки, группировки и ты не хочешь потерять этот запрос, после отключения от консоли.
SELECT запросы очень удобно сохранять как хранимые процедуры в PostgreSQL. Ты можешь дать им любое имя и вызывать когда тебе нужно.
Сохраняем простой SELECT запрос как хранимую процедуру
Для примера, представим, что у нас есть таблица app_user, которая хранит всех пользователей нашего приложения.
Если тебе нужно получить количество пользователей твоего приложения, ты напишешь что-то вроде:
SELECT count(*) FROM app_user;
Чтобы дать имя этому SQL запросу, ты можешь сохранить его как stored procedure.
create or replace function count_app_users()
    returns table
            (
                total bigint
            )
    language plpgsql
as
$$
begin
    return query (
      select count(*) from app_user
    );
end;
$$;
Давай разберем пример по шагам:
- В первой строке мы создаем функцию и называем ее 
count_app_users. - Команда 
create or replaceиспользуется для того, чтобы создать функцию, если она не существует, или обновить ее, если она уже есть. Если ты уверен в том, что функции еще нет, можешь использовать обычныйcreate - Дальше мы явно указываем тип результата. Она равен 
tableдля всех SELECT запросов. - В скобках мы указываем столбцы итоговой таблицы и их тип.
 - Команду 
language plpgsqlи несколько следующих строк ты можешь рассматривать как шаблон и не слишком вникать в них - Сам запрос мы помещаем внутрь выражения 
return query(...)которое обернуто в$$ beginиend $$ 
Вызываем хранимую процедуру
После того, как мы сохранили SELECT запрос, мы можем вызвать его.
SELECT * FROM count_app_users();
Такой синтаксис получается потому, что процедура возвращает таблицу. Чтобы получить все ее строки мы выполняем обычный SELECT *
Передаем аргументы в хранимую процедуру PostgreSQL
Дальше, представим, что мы хотим посчитать только пользователей созданных после определенной даты.
Чтобы сделать это, нам нужно передать параметра внутрь хранимой процедуры и использовать его внутри SELECT запроса. В сигнатуру процедуры, мы добавим имя параметров и их типы. Потом, внутри SELECT, мы обратимся к ним.
create or replace function count_app_users(after timestamp) -- declaring the "after" parameter
    returns table
            (
                total bigint
            )
    language plpgsql
as
$$
begin
    return query (
      select count(*) from app_user
      where created_at > after                              -- using the "after" parameter
    );
end;
$$;
Теперь мы можем передать дату в процедуру count_app_users и получить количество всех пользователей созданных после этой даты.
SELECT * FROM count_app_users('2020-11-01');
Удаление хранимой процедуры PostgreSQL
Если ты попробуешь вызывать функцию count_app_users без аргументов, она будет до сих пор работать. Это получается потому, что функция определяется не только именем, но и типом и количеством параметров.
И у нас получилось две функции. Первая — count_app_users, вторая — count_app_users(after). Обе они отлично живут в SQL базе данных.
Если одна из них тебе не нужна, то ее можно удалить так:
DROP function count_app_users();
Синтаксис похож на удаление таблицы и в нем нет каких-то подводных камней.
Посмотреть текст хранимой процедуры PostgreSQL
Наш пример с count намеренно максимально упрощен, но если у тебя более длинная функция, то бывает полезно получить ее текст. Например, чтобы понять, как она работает или чтобы дальше его изменить.
Чтобы получить текст хранимой процедуры в PostgreSQL, тебе нужно знать ее имя:
SELECT prosrc
FROM pg_proc
WHERE proname = 'count_app_users';
Такой запрос вернет нам все, что мы писали между begin и end при создании.
Разбираемся с частыми ошибками
Первая ошибка с которой ты можешь столкнуться при сохранении SELECT запроса выглядит примерно так:
ERROR: query has no destination for result data
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function count_app_users() line 3 at SQL statement
Она возникает если ты не оберешь SELECT запрос в выражение return query(...).
Дальше, может быть ошибка связанная с неправильным вызовом хранимой процедуры в PostgreSQL:
ERROR: cannot use RETURN QUERY in a non-SETOF function
Скорее всего ты попробовал вызывать функцию так:
call count_app_users();
Или так:
SELECT count_app_users();
Так как наша функция возвращала таблицу, то и результат из этой таблицы нужно получать как из любых других. С помощью SELECT.
SELECT * FROM count_app_users();
Заключение
Сохранять SELECT запросы в хранимые процедуры PostgreSQL очень удобно. Потом их можно вызывать по имени и не нужно будет каждый раз писать запрос с нуля.
Мы разобрали очень простой пример, но ты можешь сохранять SELECT запросы любой сложности.
Используй джойны, агрегацию, фильтр, сортировку и больше не пиши одни и те же запросы раз за разом.