43.6. Управляющие структуры
Управляющие структуры, вероятно, наиболее полезная и важная часть PL/pgSQL. С их помощью можно очень гибко и эффективно манипулировать данными PostgreSQL.
43.6.1. Команды для возврата значения из функции
Две команды позволяют вернуть данные из функции: RETURN
и RETURN NEXT
.
43.6.1.1. 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); -- требуется приведение типов
43.6.1.2. RETURN NEXT
и RETURN QUERY
RETURN NEXTвыражение
; RETURN QUERYзапрос
; RETURN QUERY EXECUTEстрока-команды
[USINGвыражение
[, ...]];
Для функций на PL/pgSQL, возвращающих SETOF
, нужно действовать несколько по-иному. Отдельные элементы возвращаемого значения формируются командами некий_тип
RETURN NEXT
или RETURN QUERY
, а финальная команда RETURN
без аргументов завершает выполнение функции. RETURN NEXT
используется как со скалярными, так и с составными типами данных. Для составного типа результат функции возвращается в виде таблицы. RETURN QUERY
добавляет результат выполнения запроса к результату функции. RETURN NEXT
и RETURN QUERY
можно свободно смешивать в теле функции, в этом случае их результаты будут объединены.
RETURN NEXT
и RETURN QUERY
не выполняют возврат из функции. Они просто добавляют строки в результирующее множество. Затем выполнение продолжается со следующего оператора в функции. Успешное выполнение RETURN NEXT
и RETURN QUERY
формирует множество строк результата. Для выхода из функции используется RETURN
, обязательно без аргументов (или можно просто дождаться окончания выполнения функции).
RETURN QUERY
имеет разновидность RETURN QUERY EXECUTE
, предназначенную для динамического выполнения запроса. В текст запроса можно добавить параметры, используя USING
, так же как и с командой EXECUTE
.
Для функции с выходными параметрами просто используйте RETURN NEXT
без аргументов. При каждом исполнении RETURN NEXT
текущие значения выходных параметров сохраняются для последующего возврата в качестве строки результата. Обратите внимание, что если функция с выходными параметрами должна возвращать множество значений, то при объявлении нужно указывать RETURNS SETOF
. При этом если выходных параметров несколько, то используется RETURNS SETOF record
, а если только один с типом некий_тип
, то RETURNS SETOF
.некий_тип
Пример использования 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; -- возвращается текущая строка запроса 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 'Нет рейсов на дату: %.', $1; END IF; RETURN; END; $BODY$ LANGUAGE plpgsql; -- Возвращает доступные рейсы либо вызывает исключение, если таковых нет. SELECT * FROM get_available_flightid(CURRENT_DATE);
Примечание
В текущей реализации RETURN NEXT
и RETURN QUERY
результирующее множество накапливается целиком, прежде чем будет возвращено из функции. Если множество очень большое, то это может отрицательно сказаться на производительности, так как при нехватке оперативной памяти данные записываются на диск. В следующих версиях PL/pgSQL это ограничение будет снято. В настоящее время управлять количеством оперативной памяти в подобных случаях можно параметром конфигурации work_mem. При наличии свободной памяти администраторы должны рассмотреть возможность увеличения значения данного параметра.
43.6.2. Завершение процедуры
Процедура не возвращает никакого значения, поэтому она может завершаться без оператора RETURN
. Если вы хотите досрочно завершить выполнение кода оператором RETURN
, напишите просто RETURN
без возвращаемого выражения.
Если у процедуры есть выходные параметры, конечные значения соответствующих им переменных будут выданы вызывающему коду.
43.6.3. Вызов процедуры
Функция, процедура или блок DO
в PL/pgSQL может вызвать процедуру, используя оператор CALL
. Выходные параметры при этом обрабатываются не так, как это делает CALL
в обычном SQL. Каждому параметру 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; -- выводится 15 END; $$;
43.6.4. Условные операторы
Операторы 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
43.6.4.1. IF-THEN
IFлогическое-выражение
THENоператоры
END 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;
43.6.4.2. IF-THEN-ELSE
IFлогическое-выражение
THENоператоры
ELSEоператоры
END 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;
43.6.4.3. IF-THEN-ELSIF
IFлогическое-выражение
THENоператоры
[ELSIFлогическое-выражение
THENоператоры
[ELSIFлогическое-выражение
THENоператоры
...]] [ELSEоператоры
] END IF;
В некоторых случаях двух альтернатив недостаточно. IF-THEN-ELSIF
обеспечивает удобный способ проверки нескольких вариантов по очереди. Условия в IF
последовательно проверяются до тех пор, пока не будет найдено первое истинное. После этого операторы, относящиеся к этому условию, выполняются, и управление переходит к следующей после END 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
.
43.6.4.4. Простой CASE
CASEвыражение-поиска
WHENвыражение
[,выражение
[...]] THENоператоры
[WHENвыражение
[,выражение
[...]] THENоператоры
...] [ELSEоператоры
] END CASE;
Простая форма CASE
реализует условное выполнение на основе сравнения операндов. Выражение-поиска
вычисляется (один раз) и последовательно сравнивается с каждым выражением
в условиях WHEN
. Если совпадение найдено, то выполняются соответствующие операторы
и управление переходит к следующей после END CASE
команде. (Все последующие выражения WHEN
не проверяются.) Если совпадение не было найдено, то выполняются операторы
в ELSE
. Но если ELSE
нет, то вызывается исключение CASE_NOT_FOUND
.
Пример:
CASE x WHEN 1, 2 THEN msg := 'один или два'; ELSE msg := 'значение, отличное от один или два'; END CASE;
43.6.4.5. CASE
с перебором условий
CASE WHENлогическое-выражение
THENоператоры
[WHENлогическое-выражение
THENоператоры
...] [ELSEоператоры
] END CASE;
Эта форма CASE
реализует условное выполнение, основываясь на истинности логических условий. Каждое логическое-выражение
в предложении WHEN
вычисляется по порядку до тех пор, пока не будет найдено истинное. Затем выполняются соответствующие операторы
и управление переходит к следующей после END CASE
команде. (Все последующие выражения WHEN
не проверяются.) Если ни одно из условий не окажется истинным, то выполняются операторы
в ELSE
. Но если ELSE
нет, то вызывается исключение CASE_NOT_FOUND
.
Пример:
CASE WHEN x BETWEEN 0 AND 10 THEN msg := 'значение в диапазоне между 0 и 10'; WHEN x BETWEEN 11 AND 20 THEN msg := 'значение в диапазоне между 11 и 20'; END CASE;
Эта форма CASE
полностью эквивалента IF-THEN-ELSIF
, за исключением того, что при невыполнении всех условий и отсутствии ELSE
, IF-THEN-ELSIF
ничего не делает, а CASE
вызывает ошибку.
43.6.5. Простые циклы
Операторы LOOP
, EXIT
, CONTINUE
, WHILE
, FOR
и FOREACH
позволяют повторить серию команд в функции на PL/pgSQL.
43.6.5.1. LOOP
[<<метка
>>] LOOPоператоры
END LOOP [метка
];
LOOP
организует безусловный цикл, который повторяется до бесконечности, пока не будет прекращён операторами EXIT
или RETURN
. Для вложенных циклов можно использовать метку
в операторах EXIT
и CONTINUE
, чтобы указать, к какому циклу эти операторы относятся.
43.6.5.2. EXIT
EXIT [метка
] [WHENлогическое-выражение
];
Если метка
не указана, то завершается самый внутренний цикл, далее выполняется оператор, следующий за END LOOP
. Если метка
указана, то она должна относиться к текущему или внешнему циклу, или это может быть метка блока. При этом в именованном цикле/блоке выполнение прекращается, а управление переходит к следующему оператору после соответствующего END
.
При наличии WHEN
цикл прекращается, только если логическое-выражение
истинно. В противном случае управление переходит к оператору, следующему за EXIT
.
EXIT
можно использовать со всеми типами циклов, не только с безусловным.
Когда EXIT
используется для выхода из блока, управление переходит к следующему оператору после окончания блока. Обратите внимание, что для выхода из блока нужно обязательно указывать метку
. EXIT
без метки
не позволяет прекратить работу блока. (Это изменение по сравнению с версиями PostgreSQL до 8.4, в которых разрешалось использовать EXIT
без метки
для прекращения работы текущего блока.)
Примеры:
LOOP -- здесь производятся вычисления IF count > 0 THEN EXIT; -- выход из цикла END IF; END LOOP; LOOP -- здесь производятся вычисления EXIT WHEN count > 0; -- аналогично предыдущему примеру END LOOP; <<ablock>> BEGIN -- здесь производятся вычисления IF stocks > 100000 THEN EXIT ablock; -- выход из блока BEGIN END IF; -- вычисления не будут выполнены, если stocks > 100000 END;
43.6.5.3. CONTINUE
CONTINUE [метка
] [WHENлогическое-выражение
];
Если метка
не указана, то начинается следующая итерация самого внутреннего цикла. То есть все оставшиеся в цикле операторы пропускаются, и управление переходит к управляющему выражению цикла (если есть) для определения, нужна ли ещё одна итерация цикла. Если метка
присутствует, то она указывает на метку цикла, выполнение которого будет продолжено.
При наличии WHEN
следующая итерация цикла начинается только тогда, когда логическое-выражение
истинно. В противном случае управление переходит к оператору, следующему за CONTINUE
.
CONTINUE
можно использовать со всеми типами циклов, не только с безусловным.
Примеры:
LOOP -- здесь производятся вычисления EXIT WHEN count > 100; CONTINUE WHEN count < 50; -- вычисления для count в диапазоне 50 .. 100 END LOOP;
43.6.5.4. WHILE
[<<метка
>>] WHILEлогическое-выражение
LOOPоператоры
END LOOP [метка
];
WHILE
выполняет серию команд до тех пор, пока истинно логическое-выражение
. Выражение проверяется непосредственно перед каждым входом в тело цикла.
Пример:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP -- здесь производятся вычисления END LOOP; WHILE NOT done LOOP -- здесь производятся вычисления END LOOP;
43.6.5.5. FOR
(целочисленный вариант)
[<<метка
>>] FORимя
IN [REVERSE]выражение
..выражение
[BYвыражение
] LOOPоператоры
END LOOP [метка
];
В этой форме цикла FOR
итерации выполняются по диапазону целых чисел. Переменная имя
автоматически определяется с типом integer
и существует только внутри цикла (если уже существует переменная с таким именем, то внутри цикла она будет игнорироваться). Выражения для нижней и верхней границы диапазона чисел вычисляются один раз при входе в цикл. Если не указано BY
, то шаг итерации 1, в противном случае используется значение в BY
, которое вычисляется, опять же, один раз при входе в цикл. Если указано REVERSE
, то после каждой итерации величина шага вычитается, а не добавляется.
Примеры целочисленного FOR
:
FOR i IN 1..10 LOOP -- внутри цикла переменная i будет иметь значения 1,2,3,4,5,6,7,8,9,10 END LOOP; FOR i IN REVERSE 10..1 LOOP -- внутри цикла переменная i будет иметь значения 10,9,8,7,6,5,4,3,2,1 END LOOP; FOR i IN REVERSE 10..1 BY 2 LOOP -- внутри цикла переменная i будет иметь значения 10,8,6,4,2 END LOOP;
Если нижняя граница цикла больше верхней границы (или меньше, в случае REVERSE
), то тело цикла не выполняется вообще. При этом ошибка не возникает.
Если с циклом FOR
связана метка
, к целочисленной переменной цикла можно обращаться по имени, указывая эту метку
.
43.6.6. Цикл по результатам запроса
Другой вариант FOR
позволяет организовать цикл по результатам запроса. Синтаксис:
[ <<метка
>> ] FORцель
INзапрос
LOOPоператоры
END LOOP [метка
];
Переменная цель
может быть переменной-кортежем, переменной типа record
или разделённым запятыми списком скалярных переменных. Переменной цель
последовательно присваиваются строки результата запроса, и для каждой строки выполняется тело цикла. Пример:
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 'Refreshing materialized view %.% (owner: %)...', 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 'Done refreshing materialized views.'; RETURN 1; END; $$ LANGUAGE plpgsql;
Если цикл завершается по команде EXIT
, то последняя присвоенная строка доступна и после цикла.
В качестве запроса
в этом типе оператора FOR
может задаваться любая команда SQL, возвращающая строки. Чаще всего это SELECT
, но также можно использовать и INSERT
, UPDATE
или DELETE
с предложением RETURNING
. Кроме того, возможно применение и некоторых служебных команд, например EXPLAIN
.
Для переменных PL/pgSQL в тексте запроса выполняется подстановка значений, план запроса кешируется для возможного повторного использования, как подробно описано в Подразделе 43.11.1 и Подразделе 43.11.2.
Ещё одна разновидность этого типа цикла FOR-IN-EXECUTE
:
[ <<метка
>> ] FORцель
IN EXECUTEвыражение_проверки
[ USINGвыражение
[, ... ] ] LOOPоператоры
END LOOP [метка
];
Она похожа на предыдущую форму, за исключением того, что текст запроса указывается в виде строкового выражения. Текст запроса формируется и для него строится план выполнения при каждом входе в цикл. Это даёт программисту выбор между скоростью предварительно разобранного запроса и гибкостью динамического запроса, так же, как и в случае с обычным оператором EXECUTE
. Как и в EXECUTE
, значения параметров могут быть добавлены в команду с использованием USING
.
Ещё один способ организовать цикл по результатам запроса это объявить курсор. Описание в Подразделе 43.7.4.
43.6.7. Цикл по элементам массива
Цикл FOREACH
очень похож на FOR
. Отличие в том, что вместо перебора строк SQL-запроса происходит перебор элементов массива. (В целом, FOREACH
предназначен для перебора выражений составного типа. Варианты реализации цикла для работы с прочими составными выражениями помимо массивов могут быть добавлены в будущем.) Синтаксис цикла FOREACH
:
[ <<метка
>> ] FOREACHцель
[ SLICEчисло
] IN ARRAYвыражение
LOOPоператоры
END LOOP [метка
];
Без указания SLICE
, или если SLICE
равен 0, цикл выполняется по всем элементам массива, полученного из выражения
. Переменной цель
последовательно присваивается каждый элемент массива и для него выполняется тело цикла. Пример цикла по элементам целочисленного массива:
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;
Обход элементов проводится в том порядке, в котором они сохранялись, независимо от размерности массива. Как правило, цель
это одиночная переменная, но может быть и списком переменных, когда элементы массива имеют составной тип (записи). В этом случае переменным присваиваются значения из последовательных столбцов составного элемента массива.
При положительном значении SLICE
FOREACH
выполняет итерации по срезам массива, а не по отдельным элементам. Значение SLICE
должно быть целым числом, не превышающим размерности массива. Переменная цель
должна быть массивом, который получает последовательные срезы исходного массива, где размерность каждого среза задаётся значением 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}
43.6.8. Обработка ошибок
По умолчанию любая возникающая ошибка прерывает выполнение функции на PL/pgSQL и транзакцию, в которой она выполняется. Использование в блоке секции EXCEPTION
позволяет перехватывать и обрабатывать ошибки. Синтаксис секции EXCEPTION
расширяет синтаксис обычного блока:
[ <<метка
>> ] [ DECLAREобъявления
] BEGINоператоры
EXCEPTION WHENусловие
[ ORусловие
... ] THENоператоры_обработчика
[ WHENусловие
[ ORусловие
... ] THENоператоры_обработчика
... ] END;
Если ошибок не было, то выполняются все операторы
блока и управление переходит к следующему оператору после END
. Но если при выполнении оператора
происходит ошибка, то дальнейшая обработка прекращается и управление переходит к списку исключений в секции EXCEPTION
. В этом списке ищется первое исключение, условие которого соответствует ошибке. Если исключение найдено, то выполняются соответствующие операторы_обработчика
и управление переходит к следующему оператору после END
. Если исключение не найдено, то ошибка передаётся наружу, как будто секции EXCEPTION
не было. При этом ошибку можно перехватить в секции EXCEPTION
внешнего блока. Если ошибка так и не была перехвачена, то обработка функции прекращается.
В качестве условия
может задаваться одно из имён, перечисленных в Приложении A. Если задаётся имя категории, ему соответствуют все ошибки в данной категории. Специальному имени условия OTHERS
(другие) соответствуют все типы ошибок, кроме QUERY_CANCELED
и ASSERT_FAILURE
. (И эти два типа ошибок можно перехватить по имени, но часто это неразумно.) Имена условий воспринимаются без учёта регистра. Условие ошибки также можно задать кодом SQLSTATE
; например, эти два варианта равнозначны:
WHEN division_by_zero THEN ... WHEN SQLSTATE '22012' THEN ...
Если при выполнении операторов_обработчика
возникнет новая ошибка, то она не может быть перехвачена в этой секции 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 'перехватили ошибку division_by_zero'; RETURN x; END;
При присваивании значения переменной y
произойдёт ошибка division_by_zero
. Она будет перехвачена в секции EXCEPTION
. Оператор RETURN
вернёт значение x
, увеличенное на единицу, но изменения сделанные командой UPDATE
будут отменены. Изменения, выполненные командой INSERT
, которая предшествует блоку, не будут отменены. В результате, база данных будет содержать Tom Jones
, а не Joe Jones
.
Подсказка
Наличие секции EXCEPTION
значительно увеличивает накладные расходы на вход/выход из блока, поэтому не используйте EXCEPTION
без надобности.
Пример 43.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
в функции триггера для этой таблицы. Некорректное поведение также возможно, если в таблице будет несколько уникальных индексов; тогда операция будет повторяться вне зависимости от того, нарушение какого индекса вызвало ошибку. Используя средства, рассмотренные далее, можно сделать код более надёжным, проверяя, что перехвачена именно ожидаемая ошибка.
43.6.8.1. Получение информации об ошибке
При обработке исключений часто бывает необходимым получить детальную информацию о произошедшей ошибке. Для этого в PL/pgSQL есть два способа: использование специальных переменных и команда GET STACKED DIAGNOSTICS
.
Внутри секции EXCEPTION
специальная переменная SQLSTATE
содержит код ошибки, для которой было вызвано исключение (список возможных кодов ошибок приведён в Таблице A.1). Специальная переменная SQLERRM
содержит сообщение об ошибке, связанное с исключением. Эти переменные являются неопределёнными вне секции EXCEPTION
.
Также в обработчике исключения можно получить информацию о текущем исключении командой GET STACKED DIAGNOSTICS
, которая имеет вид:
GET STACKED DIAGNOSTICSпеременная
{ = | := }элемент
[ , ... ];
Каждый элемент
представляется ключевым словом, указывающим, какое значение состояния нужно присвоить заданной переменной
(она должна иметь подходящий тип данных, чтобы принять его). Доступные в настоящее время элементы состояния показаны в Таблице 43.2.
Таблица 43.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 | строки текста, описывающие стек вызовов в момент исключения (см. Подраздел 43.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;
43.6.9. Получение информации о месте выполнения
Команда GET DIAGNOSTICS
, ранее описанная в Подразделе 43.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'--- Стек вызова ---\n%', stack; RETURN 1; END; $$ LANGUAGE plpgsql; SELECT outer_func(); NOTICE: --- Стек вызова --- 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
возвращает похожий стек вызовов, но описывает не текущее место, а место, в котором произошла ошибка.