Управляющие структуры являются, пожалуй, наиболее полезной (и важной) частью PL/pgSQL. С помощью PL/pgSQLуправляющих структур можно обрабатывать Digital Q.DataBase данные весьма гибким и эффективным способом.
Для возврата данных из функции доступны две команды: RETURN и RETURN
NEXT.
RETURN #
RETURN выражение;
RETURN с выражением завершает выполнение функции и возвращает значение
выражение вызывающей стороне. Эта форма
используется для PL/pgSQL функций, не возвращающих набор данных.
В функции, возвращающей скалярный тип данных, результат выражения будет автоматически приведён к возвращаемому типу функции по правилам присваивания. Для возврата же составного значения (строки) необходимо написать выражение, формирующее именно тот набор столбцов, который был запрошен. Это может потребовать использования явного приведения типов.
Если функция была объявлена с выходными параметрами, следует указать только
RETURN без выражения. Будут возвращены текущие значения
переменных, соответствующих выходным параметрам.
Если функция была объявлена как возвращающая void, для
RETURN досрочного выхода из функции можно использовать инструкцию; но после неё не следует указывать выражение
RETURN.
Возвращаемое значение функции не может оставаться неопределённым. Если
управление достигает конца блока верхнего уровня функции
без выполнения инструкции RETURN инструкции, произойдет ошибка времени выполнения. Данное ограничение не распространяется на функции
с выходными параметрами и функции, возвращающие void,
однако. В таких случаях инструкция RETURN выполняется автоматически по завершении блока верхнего уровня.
Некоторые примеры:
-- функции, возвращающие скалярный тип RETURN 1 + 2; RETURN scalar_var; -- функции, возвращающие составной тип RETURN composite_type_var; RETURN (1, 2, 'three'::text); -- необходимо привести столбцы к соответствующим типам
RETURN NEXT и RETURN QUERY #RETURN NEXTвыражение; RETURN QUERYзапрос; RETURN QUERY EXECUTEстрока-команды[ USINGвыражение[, ... ] ];
Когда PL/pgSQL функция объявлена как возвращающая тип данных
SETOF , процедура
выполнения будет несколько иной. В этом случае отдельные элементы для возврата задаются последовательностью команд sometypeRETURN
NEXT или RETURN QUERY , а затем финальная
команда RETURN без аргумента
используется для индикации завершения выполнения функции.
RETURN NEXT можно использовать как со скалярными, так и с составными типами данных; при использовании составного типа результата будет возвращена вся
«таблица» набора результатов.
RETURN QUERY добавляет результаты выполнения запроса к результирующему набору функции. RETURN
NEXT и RETURN QUERY могут свободно
перемешиваться в одной функции, возвращающей набор строк, и в этом случае
их результаты будут объединены.
RETURN NEXT и RETURN
QUERY на самом деле не вызывают выход из функции — они просто добавляют ноль или более строк в результирующий набор функции. Затем выполнение продолжается со следующего оператора в
PL/pgSQL функции. По мере того как выполняются
RETURN NEXT или RETURN
QUERY последующие команды, формируется результирующий набор.
Завершающий оператор RETURN, который не должен иметь аргументов, вызывает выход из функции (также можно просто позволить управлению достичь конца функции).
RETURN QUERY имеет вариант
RETURN QUERY EXECUTE, который определяет запрос, подлежащий динамическому выполнению. Выражения параметров могут
быть вставлены в вычисляемую строку запроса с помощью USING,
точно так же, как в EXECUTE команде.
Если функция была объявлена с выходными параметрами, следует указать только
RETURN NEXT без выражения. При каждом выполнении текущие значения переменных выходных параметров будут сохраняться для последующего возврата в виде строки результата. Обратите внимание, что функцию необходимо объявить с типом возвращаемого значения
SETOF record при наличии нескольких выходных параметров или SETOF
когда имеется только один выходной параметр типа
sometypesometype, чтобы создать возвращающую набор функцию с выходными параметрами.
Ниже приведен пример функции, использующей RETURN
NEXT:
CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');
CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN
SELECT * FROM foo WHERE fooid > 0
LOOP
-- здесь можно выполнить некоторую обработку
RETURN NEXT r; -- возврат текущей строки SELECT
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
SELECT * FROM get_all_foo();
Ниже приведен пример функции, использующей RETURN
QUERY:
CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
RETURN QUERY SELECT flightid
FROM flight
WHERE flightdate >= $1
AND flightdate < ($1 + 1);
-- Поскольку выполнение еще не завершено, можно проверить, были ли возвращены строки,
-- и вызвать исключение, если нет.
IF NOT FOUND THEN
RAISE EXCEPTION 'No flight at %.', $1; END IF; RETURN; END; $BODY$ LANGUAGE plpgsql; -- Возвращает доступные рейсы или вызывает исключение, если доступных рейсов нет. SELECT * FROM get_available_flightid(CURRENT_DATE);
Текущая реализация RETURN NEXT
и RETURN QUERY сохраняет весь результирующий набор
перед выходом из функции, как было описано выше. Это
означает, что если PL/pgSQL функция формирует
очень большой результирующий набор, производительность может снизиться: данные будут
записываться на диск во избежание исчерпания памяти, однако сама функция
не завершит работу до тех пор, пока не будет сформирован весь
набор результатов. В будущей версии PL/pgSQL может появиться возможность
определять функции, возвращающие наборы данных,
не имеющие данного ограничения. В настоящее время момент,
в который данные начинают записываться на диск, определяется
work_mem
конфигурационным параметром. Администраторам, располагающим достаточным объемом
памяти для хранения больших результирующих наборов, следует рассмотреть возможность
увеличения этого параметра.
Процедура не имеет возвращаемого значения. Следовательно, процедура может завершиться
без использования оператора RETURN RETURN. Для использования
инструкции RETURN в целях досрочного выхода из кода следует указать
только RETURN без выражения.
Если процедура имеет выходные параметры, вызывающей стороне будут возвращены конечные значения соответствующих переменных.
A PL/pgSQL функция, процедура
или DO блок может вызывать процедуру
с помощью команды CALL. Выходные параметры обрабатываются иначе, чем в команде CALL обычного
SQL. Каждый OUT или INOUT
параметр процедуры должен
соответствовать переменной в инструкции CALL инструкция, при этом любое значение, возвращаемое процедурой, присваивается этой переменной после завершения выполнения. Например:
CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
x := x * 3;
END;
$$;
DO $$
DECLARE myvar int := 5;
BEGIN
CALL triple(myvar);
RAISE NOTICE 'myvar = %', myvar; -- prints 15
END;
$$;
Переменная, соответствующая выходному параметру, может быть простой переменной или полем переменной составного типа. В настоящее время она не может быть элементом массива.
IF и CASE операторы позволяют выполнять альтернативные команды в зависимости от определенных условий.
PL/pgSQL имеет три формы IF:
IF ... THEN ... END IF
IF ... THEN ... ELSE ... END IF
IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF
и две формы CASE:
CASE ... WHEN ... THEN ... ELSE ... END CASE
CASE WHEN ... THEN ... ELSE ... END CASE
IF-THEN #IFboolean-expressionTHENstatementsEND IF;
IF-THEN операторы представляют собой простейшую форму
IF. Операторы, расположенные между
THEN и END IF будут
выполнены, если условие истинно. В противном случае они
пропускаются.
Пример:
IF v_user_id <> 0 THEN
UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;
IF-THEN-ELSE #IFboolean-expressionTHENstatementsELSEstatementsEND IF;
IF-THEN-ELSE операторы дополняют конструкцию
IF-THEN позволяя указать
альтернативный набор операторов, которые должны быть выполнены, если
условие ложно. (Следует учесть, что сюда относится и случай, когда
условие принимает значение NULL.)
Примеры:
IF parentid IS NULL OR parentid = ''
THEN
RETURN fullname;
ELSE
RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;
IF v_count > 0 THEN
INSERT INTO users_count (count) VALUES (v_count);
RETURN 't';
ELSE
RETURN 'f';
END IF;
IF-THEN-ELSIF #IFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements... ] ] [ ELSEstatements] END IF;
Иногда количество альтернатив может быть больше двух.
IF-THEN-ELSIF предоставляет удобный
метод последовательной проверки нескольких альтернатив.
IF условия проверяются последовательно
до тех пор, пока не будет найдено первое истинное значение. После этого
выполняются соответствующие операторы, а управление
передается следующему за конструкцией оператору END IF.
(Все последующие IF условия не
проверяются.) Если ни одно из IF условий не является истинным,
тогда выполняется ELSE блок (если он определен).
Ниже приведен пример:
IF number = 0 THEN
result := 'zero';
ELSIF number > 0 THEN
result := 'positive';
ELSIF number < 0 THEN
result := 'negative';
ELSE
-- хм, единственная оставшаяся возможность — значение number равно null
result := 'NULL';
END IF;
Ключевое слово ELSIF также может записываться как
ELSEIF.
Альтернативным способом решения той же задачи является вложение
IF-THEN-ELSE инструкций, как показано в
следующем примере:
IF demo_row.sex = 'm' THEN
pretty_sex := 'man';
ELSE
IF demo_row.sex = 'f' THEN
pretty_sex := 'woman';
END IF;
END IF;
Однако этот метод требует написания соответствующей завершающей конструкции END IF
для каждого условия IF, поэтому он гораздо более громоздок, чем
использование ELSIF в тех случаях, когда имеется много альтернатив.
CASE #CASEsearch-expressionWHENвыражение[,выражение[ ... ]] THENstatements[ WHENвыражение[,выражение[ ... ]] THENstatements... ] [ ELSEstatements] END CASE;
Простая форма оператора CASE обеспечивает условное выполнение
на основе равенства операндов. Выражение search-expression
вычисляется (один раз) и последовательно сравнивается с каждым значением
выражение в WHEN предложениях.
При обнаружении совпадения выполняются соответствующие инструкции,
statements после чего управление передается за пределы
передается следующему за конструкцией оператору END CASE. (Последующие
WHEN выражения не вычисляются.) Если совпадений не
найдено, то ELSE statements выполняются;
выполняются; но если ELSE отсутствует, то
CASE_NOT_FOUND генерируется исключение.
Ниже приведен простой пример:
CASE x
WHEN 1, 2 THEN
msg := 'one or two';
ELSE
msg := 'other value than one or two';
END CASE;
CASE #
CASE
WHEN boolean-expression THEN
statements
[ WHEN boolean-expression THEN
statements
... ]
[ ELSE
statements ]
END CASE;
Поисковая форма CASE обеспечивает условное выполнение
основана на истинности логических выражений. Каждое WHEN предложение
boolean-expression вычисляется последовательно,
пока не будет найдено то, которое возвращает значение true. Затем
соответствующий statements выполняются, и
затем управление переходит к следующему оператору после END CASE.
(Последующие WHEN выражения не вычисляются.)
Если истинный результат не найден, то ELSE
statements выполняются;
но если ELSE отсутствует, то
CASE_NOT_FOUND генерируется исключение.
Ниже приведен пример:
CASE
WHEN x BETWEEN 0 AND 10 THEN
msg := 'value is between zero and ten';
WHEN x BETWEEN 11 AND 20 THEN
msg := 'value is between eleven and twenty';
END CASE;
Эта форма оператора CASE полностью эквивалентна
IF-THEN-ELSIF, за исключением правила, согласно которому достижение
пропущенного ELSE предложения приводит к ошибке, а не
к отсутствию каких-либо действий.
С помощью операторов LOOP, EXIT,
CONTINUE, WHILE, FOR,
а также FOREACH можно организовать в
PL/pgSQL функции повторение последовательности команд.
LOOP #[ <<label>> ] LOOPstatementsEND LOOP [label];
LOOP определяет безусловный цикл, который повторяется
бесконечно до тех пор, пока не будет прерван оператором EXIT или
RETURN оператором. Необязательную метку
label могут использовать операторы EXIT
и CONTINUE во вложенных циклах для
указания того, к какому именно циклу они относятся.
EXIT #EXIT [label] [ WHENboolean-expression];
Если метка не указана label указана, то самый внутренний
цикл завершается, и оператор, следующий за END
LOOP выполняется следующим. Если label
указана, она должна быть меткой текущего или некоторого внешнего
уровня вложенности циклов или блоков. В этом случае именованный цикл или блок
завершается, и выполнение продолжается с оператора, следующего после
соответствующего конца цикла или блока END.
Если WHEN указано, выход из цикла происходит только в том случае, если выражение
boolean-expression истинно. В противном случае управление переходит
к оператору, следующему после EXIT.
EXIT можно использовать со всеми типами циклов; этот оператор
не ограничивается использованием только в безусловных циклах.
При использовании с
BEGIN блоком, EXIT происходит передача
управления следующему оператору после завершения блока.
Следует отметить, что для этой цели необходимо использовать метку; оператор без метки
EXIT никогда не будет считаться соответствующим
BEGIN блоку. (Это изменение по сравнению с
версиями до 8.4 Digital Q.DataBase, которые
позволило бы оператору без метки EXIT соответствовать
определённому BEGIN блоку.)
Примеры:
LOOP
-- некоторые вычисления
IF count > 0 THEN
EXIT; -- выход из цикла
END IF;
END LOOP;
LOOP
-- некоторые вычисления
EXIT WHEN count > 0; -- тот же результат, что и в предыдущем примере
END LOOP;
<>
BEGIN
-- некоторые вычисления
IF stocks > 100000 THEN
EXIT ablock; -- вызывает выход из блока BEGIN
END IF;
-- вычисления в этой части будут пропущены, если stocks > 100000
END;
CONTINUE #CONTINUE [label] [ WHENboolean-expression];
Если метка не указана label указана, начинается следующая итерация
самого внутреннего цикла. То есть все оставшиеся операторы
в теле цикла пропускаются, и управление возвращается
к выражению управления циклом (при его наличии), чтобы определить,
требуется ли выполнение следующей итерации цикла.
Если label присутствует, то она
определяет метку цикла, выполнение которого будет
продолжено.
Если WHEN указано, следующая итерация
цикла начинается только в том случае, если выражение boolean-expression принимает значение
true. В противном случае управление переходит к оператору, следующему за
CONTINUE.
CONTINUE можно использовать со всеми типами циклов; он
не ограничивается использованием только в безусловных циклах.
Примеры:
LOOP
-- some computations
EXIT WHEN count > 100;
CONTINUE WHEN count < 50;
-- some computations for count IN [50 .. 100]
END LOOP;
WHILE #[ <<label>> ] WHILEboolean-expressionLOOPstatementsEND LOOP [label];
WHILE оператор повторяет
последовательность операторов до тех пор, пока значение
boolean-expression
выражения истинно (true). Выражение проверяется непосредственно перед
каждым входом в тело цикла.
Например:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- some computations here
END LOOP;
WHILE NOT done LOOP
-- some computations here
END LOOP;
FOR (целочисленный вариант) #[ <<label>> ] FORимяIN [ REVERSE ]выражение..выражение[ BYвыражение] LOOPstatementsEND LOOP [label];
Эта форма оператора FOR создает цикл для итерации по диапазону
целочисленных значений. Переменная
имя автоматически определяется как тип данных
integer и существует только внутри цикла (любое существующее
определение имени переменной внутри этого цикла игнорируется).
Два выражения, определяющие
нижнюю и верхнюю границы диапазона, вычисляются один раз при входе в
цикл. Если BY предложение не указано, то шаг итерации
принимается равным 1, в противном случае используется значение, указанное в этом BY
предложении, которое также вычисляется один раз при входе в цикл.
Если REVERSE указано, то значение шага
вычитается, а не прибавляется после каждой итерации.
Некоторые примеры целочисленных FOR циклов:
FOR i IN 1..10 LOOP
-- i will take on the values 1,2,3,4,5,6,7,8,9,10 within the loop
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- i will take on the values 10,9,8,7,6,5,4,3,2,1 within the loop
END LOOP;
FOR i IN REVERSE 10..1 BY 2 LOOP
-- i will take on the values 10,8,6,4,2 within the loop
END LOOP;
Если нижняя граница превышает верхнюю (или меньше,
в REVERSE случае), тело цикла
не выполняется. Ошибка при этом не возникает.
Если label метка прикреплена к
FOR циклу, то к целочисленной переменной цикла можно
обращаться по квалифицированному имени, используя эту метку
label.
Используя другой тип FOR цикла, можно итерировать результаты запроса и соответствующим образом обрабатывать эти данные. Синтаксис следующий:
[ <<label>> ] FORtargetINзапросLOOPstatementsEND LOOP [label];
Предложение target является переменной типа record, переменной строки или списком скалярных переменных, разделенных запятыми. target последовательно присваивается каждая строка, возвращаемая в результате запрос а тело цикла выполняется для каждой строки. Ниже приведен пример:
CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
mviews RECORD;
BEGIN
RAISE NOTICE 'Refreshing all materialized views...';
FOR mviews IN
SELECT n.nspname AS mv_schema,
c.relname AS mv_name,
pg_catalog.pg_get_userbyid(c.relowner) AS owner
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
WHERE c.relkind = 'm'
ORDER BY 1
LOOP
-- Теперь переменная «mviews» содержит одну запись с информацией о материализованном представлении
RAISE NOTICE 'Обновление материализованного представления %.% (владелец: %)...',
quote_ident(mviews.mv_schema),
quote_ident(mviews.mv_name),
quote_ident(mviews.owner);
EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
END LOOP;
RAISE NOTICE 'Обновление материализованных представлений завершено.';
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Если выполнение цикла прерывается оператором EXIT , последнее присвоенное значение строки остается доступным и после завершения цикла.
Предложение запрос используемая в данном типе FOR
в качестве оператора может выступать любая команда SQL, возвращающая строки вызывающей стороне:
SELECT является наиболее распространенным случаем,
но также можно использовать INSERT, UPDATE,
DELETE, или MERGE с
RETURNING предложением. Некоторые вспомогательные
команды, такие как EXPLAIN также будут работать.
PL/pgSQL переменные заменяются параметрами запроса, а план запроса кэшируется для возможного повторного использования, как подробно описано в Раздел 5.6.11.1 и Раздел 5.6.11.2.
Предложение FOR-IN-EXECUTE оператор представляет собой еще один способ итерации по строкам:
[ <<label>> ] FORtargetIN EXECUTEtext_expression[ USINGвыражение[, ... ] ] LOOPstatementsEND LOOP [label];
Эта форма аналогична предыдущей, за исключением того, что исходный запрос
задается в виде строкового выражения, которое вычисляется и перепланируется
при каждом входе в FOR цикл. Это позволяет программисту выбирать между скоростью заранее спланированного запроса и гибкостью динамического запроса, так же как и в случае с обычным оператором EXECUTE .
Как и в EXECUTE, значения параметров могут быть вставлены
в динамическую команду с помощью USING.
Еще один способ определить запрос, по результатам которого должна выполняться итерация, заключается в его объявлении в виде курсора. Это описано в разделе Раздел 5.6.7.4.
Предложение FOREACH цикл во многом похож на FOR цикл,
но вместо итерации по строкам, возвращаемым SQL-запросом,
он выполняет итерацию по элементам значения массива.
(В целом, FOREACH предназначен для перебора компонентов выражения с составным значением; варианты перебора других составных объектов, помимо массивов, могут быть добавлены в будущем.) FOREACH оператор для организации цикла по массиву имеет следующий вид:
[ <<label>> ] FOREACHtarget[ SLICEчисло] IN ARRAYвыражениеLOOPstatementsEND LOOP [label];
Без указания SLICE, или если SLICE 0 указано,
цикл выполняет итерацию по отдельным элементам массива, полученного
в результате вычисления выражения выражение.
Команда target переменной последовательно присваивается значение каждого элемента, и для каждого элемента выполняется тело цикла. Ниже приведен пример перебора элементов целочисленного массива:
CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
s int8 := 0;
x int;
BEGIN
FOREACH x IN ARRAY $1
LOOP
s := s + x;
END LOOP;
RETURN s;
END;
$$ LANGUAGE plpgsql;
Элементы обходятся в порядке их хранения независимо от количества размерностей массива. Хотя target обычно представляет собой одну переменную, это может быть список переменных при циклическом обходе массива составных значений (записей). В этом случае
для каждого элемента массива значения из последовательных столбцов составного значения
присваиваются соответствующим переменным.
При положительном SLICE значении FOREACH
выполняется итерация по срезам массива, а не по отдельным элементам.
Параметр SLICE должен быть целочисленной константой, значение которой не превышает количество размерностей массива.
target переменная должна иметь тип «массив»,
и в неё последовательно записываются срезы исходного массива, при этом размерность каждого среза
определяется параметром SLICE.
Ниже приведен пример итерации по одномерным срезам:
CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
x int[];
BEGIN
FOREACH x SLICE 1 IN ARRAY $1
LOOP
RAISE NOTICE 'row = %', x;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);
NOTICE: row = {1,2,3}
NOTICE: row = {4,5,6}
NOTICE: row = {7,8,9}
NOTICE: row = {10,11,12}
По умолчанию любая ошибка, возникающая в PL/pgSQL
функции, прерывает выполнение функции и внешней транзакции. Ошибки можно перехватывать и восстанавливать
работу после них, используя BEGIN блок с разделом
EXCEPTION предложением. Этот синтаксис является расширением обычного синтаксиса BEGIN блока:
[ <<label>> ] [ DECLAREобъявления] BEGINstatementsEXCEPTION WHENусловие[ ORусловие... ] THENhandler_statements[ WHENусловие[ ORусловие... ] THENhandler_statements... ] END;
Если ошибка не возникает, блок в такой форме просто выполняет все
statements, а затем управление переходит
к следующему оператору после END. Но если ошибка
происходит внутри statements, дальнейшая
обработка statements прекращается, и управление передается EXCEPTION список.
В данном списке осуществляется поиск первого условия, условие
соответствующего возникшей ошибке. Если совпадение найдено, выполняются соответствующие handler_statements операторы,
после чего управление передается следующему оператору после
END. Если совпадение не найдено, ошибка распространяется дальше, как если бы EXCEPTION предложение вовсе отсутствовало:
ошибка может быть перехвачена внешним блоком с
EXCEPTION, а при его отсутствии выполнение функции прерывается.
Предложение условие имена могут быть любыми из
приведенных в Приложение 8.1. Имя категории
соответствует любой ошибке, входящей в данную категорию. Специальное
имя условия OTHERS соответствует любому типу ошибки, за исключением
QUERY_CANCELED и ASSERT_FAILURE. (Перехват этих двух типов ошибок по имени возможен, но зачастую нецелесообразен.) Имена условий нечувствительны к регистру. Кроме того, условие ошибки можно указать
через SQLSTATE код; например, следующие записи эквивалентны:
WHEN division_by_zero THEN ... WHEN SQLSTATE '22012' THEN ...
Если в выбранном разделе EXCEPTION возникает новая ошибка
handler_statements, она не может быть перехвачена
данным EXCEPTION разделом, а передается вовне.
Окружающий EXCEPTION раздел может перехватить ее.
Когда ошибка перехватывается EXCEPTION разделом EXCEPTION,
локальные переменные PL/pgSQL функции
сохраняют значения, которые они имели на момент возникновения ошибки, но все изменения
постоянного состояния базы данных внутри блока откатываются.
В качестве примера рассмотрим следующий фрагмент:
INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
x := x + 1;
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'caught division_by_zero';
RETURN x;
END;
Когда управление доходит до операции присваивания переменной y, это приведет к ошибке с division_by_zero ошибка. Это исключение будет перехвачено EXCEPTION предложением. Значение, возвращаемое в
RETURN операторе, будет инкрементированным значением
x, но результаты выполнения команды UPDATE будут отменены. INSERT команда, предшествующая блоку, тем не менее, не откатывается, поэтому в конечном итоге база данных содержит Tom Jones не Joe Jones.
Блок, содержащий предложение EXCEPTION требует значительно больше ресурсов при входе и выходе, чем блок без него. Поэтому не следует использовать EXCEPTION без необходимости.
Пример 5.6.2. Исключения с UPDATE/INSERT
В данном примере обработка исключений используется для выполнения либо
UPDATE или INSERT, в зависимости от ситуации. Приложениям рекомендуется использовать INSERT с
ON CONFLICT DO UPDATE а не для практического применения
данного шаблона. Данный пример служит прежде всего для иллюстрации использования
PL/pgSQL управляющих структур:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- сначала попытка обновления по ключу
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
-- запись отсутствует, попытка вставки ключа
-- если другой пользователь одновременно вставит такой же ключ,
-- может возникнуть ошибка нарушения уникальности ключа
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Ничего не предпринимать и повторить цикл для выполнения UPDATE.
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
Данный программный код предполагает, что unique_violation ошибка вызвана INSERT, а не, скажем, INSERT в триггерной функции для таблицы. Код также может работать некорректно при наличии нескольких уникальных индексов для таблицы, так как операция будет повторена независимо от того, какой именно индекс вызвал ошибку. Повысить уровень надежности можно с помощью рассматриваемых далее возможностей для проверки того, что перехваченная ошибка соответствует ожидаемой.
Обработчикам исключений часто требуется идентифицировать конкретную возникшую ошибку. Существует два способа получения информации о текущем исключении в PL/pgSQL: специальные переменные и
GET STACKED DIAGNOSTICS команда.
Внутри обработчика исключений специальная переменная
SQLSTATE содержит код ошибки, соответствующий
возникшему исключению (см. Таблица 8.1.1
для ознакомления со списком возможных кодов ошибок). Специальная переменная
SQLERRM содержит текст сообщения об ошибке, связанного с данным исключением. Эти переменные не определены вне обработчиков исключений.
Внутри обработчика исключения также можно получить информацию о текущем исключении, используя команду
GET STACKED DIAGNOSTICS команду следующего формата:
GET STACKED DIAGNOSTICSпеременная{ = | := }элемент[ , ... ];
Каждый элемент — это ключевое слово, определяющее значение статуса, которое будет присвоено указанному параметру переменная
(который должен иметь подходящий тип данных для получения этого значения). Доступные в настоящее время элементы состояния приведены в Таблица 5.6.2.
Таблица 5.6.2. Элементы диагностики ошибок
| Имя | Тип | Описание |
|---|---|---|
RETURNED_SQLSTATE | text | код ошибки SQLSTATE для данного исключения |
COLUMN_NAME | text | имя столбца, относящегося к исключению |
CONSTRAINT_NAME | text | имя ограничения, относящегося к исключению |
PG_DATATYPE_NAME | text | имя типа данных, относящегося к исключению |
MESSAGE_TEXT | text | текст основного сообщения исключения |
TABLE_NAME | text | имя таблицы, относящейся к исключению |
SCHEMA_NAME | text | имя схемы, относящейся к исключению |
PG_EXCEPTION_DETAIL | text | текст детализированного сообщения исключения (при наличии) |
PG_EXCEPTION_HINT | text | текст подсказки к исключению (при наличии) |
PG_EXCEPTION_CONTEXT | text | строки текста, описывающие стек вызовов в момент возникновения исключение (см. Раздел 5.6.6.9) |
Если для элемента в исключении не было задано значение, будет возвращена пустая строка.
Ниже приведен пример:
DECLARE
text_var1 text;
text_var2 text;
text_var3 text;
BEGIN
-- некоторая обработка, которая может вызвать исключение
...
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
END;
Синтаксис GET DIAGNOSTICS команда, описанная ранее
в Раздел 5.6.5.5, извлекает сведения
о текущем состоянии выполнения (в то время как команда GET STACKED
DIAGNOSTICS , рассмотренная выше, предоставляет информацию о состоянии выполнения на момент возникновения предыдущей ошибки). Ее PG_CONTEXT
параметр состояния полезен для определения текущего места выполнения. PG_CONTEXT возвращает текстовую строку со строками,
описывающими стек вызовов. Первая строка относится к текущей
функции и текущей исполняемой GET DIAGNOSTICS
команде. Вторая и все последующие строки относятся к вызовам функций, расположенных выше в стеке вызовов. Например:
CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
RETURN inner_func();
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
stack text;
BEGIN
GET DIAGNOSTICS stack = PG_CONTEXT;
RAISE NOTICE E'--- Call Stack ---\n%', stack;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
SELECT outer_func();
NOTICE: --- Call Stack ---
PL/pgSQL function inner_func() line 5 at GET DIAGNOSTICS
PL/pgSQL function outer_func() line 3 at RETURN
CONTEXT: PL/pgSQL function outer_func() line 3 at RETURN
outer_func
------------
1
(1 row)
GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT
возвращает аналогичную трассировку стека, но описывает место, в котором была обнаружена ошибка, а не текущее местоположение.