40.6. Управляющие структуры

Управляющие структуры, вероятно, наиболее полезная и важная часть PL/pgSQL. С их помощью можно очень гибко и эффективно манипулировать данными PostgreSQL.

40.6.1. Команды для возврата значения из функции

Две команды позволяют вернуть данные из функции: RETURN и RETURN NEXT.

40.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);  -- требуется приведение типов

40.6.1.2. RETURN NEXT и RETURN QUERY

RETURN NEXT выражение;
RETURN QUERY запрос;
RETURN QUERY EXECUTE command-string [USING выражение [, ...]];

Для функций на PL/pgSQL, возвращающих SETOF sometype, нужно действовать несколько по-иному. Отдельные элементы возвращаемого значения формируются командами 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, а если только один с типом sometype, то RETURNS SETOF sometype.

Пример использования 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. При наличии свободной памяти администраторы должны рассмотреть возможность увеличения значения данного параметра.

40.6.2. Условные операторы

Операторы IF и CASE позволяют выполнять команды в зависимости от определённых условий. PL/pgSQL поддерживает три формы IF:

  • IF ... THEN

  • IF ... THEN ... ELSE

  • IF ... THEN ... ELSIF ... THEN ... ELSE

и две формы CASE:

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

40.6.2.1. IF-THEN

IF boolean-expression THEN
    statements
END IF;

IF-THEN это простейшая форма IF. Операторы между THEN и END IF выполняются, если условие (boolean-expression) истинно. В противном случае они опускаются.

Пример:

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

40.6.2.2. IF-THEN-ELSE

IF boolean-expression THEN
    statements
ELSE
    statements
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;

40.6.2.3. IF-THEN-ELSIF

IF boolean-expression THEN
    statements
[ELSIF boolean-expression THEN statements
[ELSIF boolean-expression THEN statements ...]]
[ELSE statements]
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.

40.6.2.4. Простой CASE

CASE search-expression
    WHEN выражение [, выражение [...]] THEN
      statements
  [WHEN выражение [, выражение [...]] THEN statements ...]
  [ELSE statements]
END CASE;

Простая форма CASE реализует условное выполнение на основе сравнения операндов. search-expression вычисляется (один раз) и последовательно сравнивается с каждым expression в условиях WHEN. Если совпадение найдено, то выполняются соответствующие statements и управление переходит к следующей после END CASE команде. (Все последующие выражения WHEN не проверяются.) Если совпадение не было найдено, то выполняются ELSE statements. Но если ELSE нет, то вызывается исключение CASE_NOT_FOUND.

Пример:

CASE x
    WHEN 1, 2 THEN
        msg := 'один или два';
    ELSE
        msg := 'значение, отличное от один или два';
END CASE;

40.6.2.5. CASE с перебором условий

CASE
    WHEN boolean-expression THEN
      statements
  [WHEN boolean-expression THEN statements ...]
  [ELSE statements]
END CASE;

Эта форма CASE реализует условное выполнение, основываясь на истинности логических условий. Каждое выражение boolean-expression в предложении WHEN вычисляется по порядку до тех пор, пока не будет найдено истинное. Затем выполняются соответствующие statements и управление переходит к следующей после END CASE команде. (Все последующие выражения WHEN не проверяются.) Если ни одно из условий не окажется истинным, то выполняются ELSE statements. Но если 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 вызывает ошибку.

40.6.3. Простые циклы

Операторы LOOP, EXIT, CONTINUE, WHILE, FOR и FOREACH позволяют повторить серию команд в функции на PL/pgSQL.

40.6.3.1. LOOP

[<<метка>>]
LOOP
    statements
END LOOP [ метка ];

LOOP организует безусловный цикл, который повторяется до бесконечности, пока не будет прекращён операторами EXIT или RETURN. Для вложенных циклов можно использовать label в операторах EXIT и CONTINUE, чтобы указать к какому циклу эти операторы относятся.

40.6.3.2. EXIT

EXIT [ метка ] [WHEN boolean-expression];

Если label не указана, то завершается самый внутренний цикл, далее выполняется оператор, следующий за END LOOP. Если label указана, то она должна относиться к текущему или внешнему циклу, или это может быть метка блока. При этом в именованном цикле/блоке выполнение прекращается, а управление переходит к следующему оператору после соответствующего END.

При наличии WHEN цикл прекращается, только если boolean-expression истинно. В противном случае управление переходит к оператору следующему за EXIT.

EXIT можно использовать со всеми типами циклов, не только с безусловным.

Когда EXIT используется для выхода из блока, управление переходит к следующему оператору после окончания блока. Обратите внимание, что для выхода из блока нужно обязательно указывать label. EXIT без label не позволяет прекратить работу блока. (Это изменение по сравнению с версиями PostgreSQL до 8.4, в которых разрешалось использовать EXIT без label для прекращения работы текущего блока.)

Примеры:

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;  -- выход из блока ablock
    END IF;
    -- вычисления не будут выполнены, если stocks > 100000
END;

40.6.3.3. CONTINUE

CONTINUE [ метка ] [WHEN boolean-expression];

Если label не указана, то начинается следующая итерация самого внутреннего цикла. То есть все оставшиеся в цикле операторы пропускаются, и управление переходит к управляющему выражению цикла (если есть) для определения, нужна ли ещё одна итерация цикла. Если label присутствует, то она указывает на метку цикла, выполнение которого будет продолжено.

При наличии WHEN следующая итерация цикла начинается только тогда, когда boolean-expression истинно. В противном случае управление переходит к оператору, следующему за CONTINUE.

CONTINUE можно использовать со всеми типами циклов, не только с безусловным.

Примеры:

LOOP
    -- здесь вычисления
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- Вычисления для count в диапазоне 50 .. 100
END LOOP;

40.6.3.4. WHILE

[<<метка>>]
WHILE boolean-expression LOOP
    statements
END LOOP [ метка ];

WHILE выполняет серию команд до тех пор, пока истинно выражение boolean-expression. Выражение проверяется непосредственно перед каждым входом в тело цикла.

Пример:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- здесь вычисления
END LOOP;

WHILE NOT done LOOP
    -- здесь вычисления
END LOOP;

40.6.3.5. FOR (целочисленный вариант)

[<<метка>>]
FOR имя IN [REVERSE] выражение .. выражение [BY выражение] LOOP
    statements
END LOOP [ метка ];

В этой форме цикла FOR итерации выполняются по диапазону целых чисел. Переменная name автоматически определяется с типом 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), то тело цикла не выполняется вообще. При этом ошибка не возникает.

Если у цикла есть метка, то к переменной цикла можно обращаться по имени, дополненному меткой.

40.6.4. Цикл по результатам запроса

Другой вариант FOR позволяет организовать цикл по результатам запроса. Синтаксис:

[ <<label>> ]
FOR target IN query LOOP
    statements
END LOOP [ label ];

Переменная target может быть строковой переменной, переменной типа record или разделённым запятыми списком скалярных переменных. В переменную target последовательно присваиваются строки результата запроса, и для каждой строки выполняется тело цикла. Пример:

CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Обновление материализованных представлений...';

    FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP

        -- Сейчас "mviews" содержит одну запись из cs_materialized_views

        RAISE NOTICE 'Обновляется мат. представление %s ...', quote_ident(mviews.mv_name);
        EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name);
        EXECUTE 'INSERT INTO '
                   || quote_ident(mviews.mv_name) || ' '
                   || mviews.mv_query;
    END LOOP;

    RAISE NOTICE 'Закончено обновление материализованных представлений.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Если цикл завершается по команде EXIT, то последняя присвоенная строка доступна и после цикла.

В цикле FOR можно использовать любые SQL-команды, возвращающие строки. Чаще всего это SELECT, но могут быть и INSERT, UPDATE, DELETE с предложением RETURNING. А также некоторые утилиты, например EXPLAIN.

Для переменных PL/pgSQL в тексте запроса выполняется подстановка значений, план запроса кешируется для возможного повторного использования, как подробно описано в Подразделе 40.10.1 и Подразделе 40.10.2.

Ещё одна разновидность этого типа цикла FOR-IN-EXECUTE:

[ <<label>> ]
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
    statements
END LOOP [ label ];

Она похожа на предыдущую форму, за исключением того, что текст запроса указывается в виде строкового выражения. Текст запроса формируется и для него строится план выполнения при каждом входе в цикл. Это даёт программисту выбор между скоростью предварительно разобранного запроса и гибкостью динамического запроса, так же, как и в случае с обычным оператором EXECUTE. Как и в EXECUTE, значения параметров могут быть добавлены в команду с использованием USING.

Ещё один способ организовать цикл по результатам запроса это объявить курсор. Описание в Подразделе 40.7.4.

40.6.5. Цикл по элементам массива

Цикл FOREACH очень похож на FOR. Отличие в том, что вместо перебора строк SQL-запроса происходит перебор элементов массива. (В целом, FOREACH предназначен для перебора выражений составного типа. Варианты реализации цикла для работы с прочими составными выражениями помимо массивов могут быть добавлены в будущем.) Синтаксис цикла FOREACH:

[ <<label>> ]
FOREACH target [ SLICE number ] IN ARRAY expression LOOP
    statements
END LOOP [ label ];

Без указания SLICE, или если SLICE равен 0, цикл выполняется по всем элементам массива, полученного из expression. В переменную 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}

40.6.6. Обработка ошибок

По умолчанию любая возникающая ошибка прерывает выполнение функции на PL/pgSQL, а также транзакцию, относящуюся к этой функции. Использование в блоке секции EXCEPTION позволяет перехватывать и обрабатывать ошибки. Синтаксис секции EXCEPTION дополняет синтаксис обычного блока:

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
EXCEPTION
    WHEN condition [ OR condition ... ] THEN
        handler_statements
    [ WHEN condition [ OR condition ... ] THEN
          handler_statements
      ... ]
END;

Если ошибок не было, то выполняются все statements блока и управление переходит к следующему оператору после END. Но если при выполнении statements происходит ошибка, то дальнейшая обработка прекращается и управление переходит к списку исключений в секции EXCEPTION. В этом списке ищется первое исключение, условие которого соответствует ошибке. Если исключение найдено, то выполняются соответствующие handler_statements и управление переходит к следующему оператору после END. Если исключение не найдено, то ошибка передаётся наружу, как будто секции EXCEPTION не было. При этом ошибку можно перехватить в секции EXCEPTION внешнего блока. Если ошибка так и не была перехвачена, то обработка функции прекращается.

Допустимые имена condition перечислены в Приложении A. Специальное имя OTHERS соответствует любой ошибке, за исключением QUERY_CANCELED. Можно явно обработать QUERY_CANCELED, но зачастую это неразумно. Имена исключений не чувствительны к регистру. Кроме того, condition можно указать через соответствующий SQLSTATE код. В следующем примере обе строки эквивалентны:

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

Если при выполнении handler_statements возникнет новая ошибка, то она не может быть перехвачена в этой секции 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 без надобности.

Пример 40-2. Обработка исключений для команд UPDATE/INSERT

В этом примере обработка исключений используется для того, чтобы определить какую команду выполнить UPDATE или INSERT:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- для начала UPDATE записи по значению ключа
        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 из триггерной функции таблицы. Здесь также не учтено, что у таблицы может быть несколько уникальных ключей, поэтому попытка повторить операцию будет предприниматься вне зависимости от того, уникальность какого ключа привела к ошибке. Далее будут рассмотрены возможности, позволяющие убедиться, что мы обрабатываем именно ту ошибку, которую хотели.

40.6.6.1. Получение информации об ошибке

При обработке исключений часто бывает необходимым получить детальную информацию о произошедшей ошибке. Для этого в PL/pgSQL есть два способа: использование специальных переменных и команда GET STACKED DIAGNOSTICS.

Внутри секции EXCEPTION специальная переменная SQLSTATE содержит код ошибки, для которой было вызвано исключение (список возможных кодов ошибок приведён в Таблице A-1). Специальная переменная SQLERRM содержит сообщение об ошибке, связанное с исключением. Эти переменные являются неопределёнными вне секции EXCEPTION.

Также, при обработке исключений, дополнительную информацию можно получить командой GET STACKED DIAGNOSTICS, которая имеет вид:

GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

Каждый item является ключевым словом, идентифицирующим значение состояния, которое будет присвоено указанной переменной. Переменная должна быть соответствующего типа данных. Идентификаторы, доступные в настоящее время, приведены в Таблице 40-1.

Таблица 40-1. Диагностические коды ошибок

ИмяТипОписание
RETURNED_SQLSTATEtextкод исключения, возвращаемый SQLSTATE
COLUMN_NAMEtextимя столбца, относящегося к исключению
CONSTRAINT_NAMEtextимя ограничения целостности, относящегося к исключению
PG_DATATYPE_NAMEtextимя типа данных, относящегося к исключению
MESSAGE_TEXTtextтекст основного сообщения исключения
TABLE_NAMEtextимя таблицы, относящейся к исключению
SCHEMA_NAMEtextимя схемы, относящейся к исключению
PG_EXCEPTION_DETAILtextтекст детального сообщения исключения (если есть)
PG_EXCEPTION_HINTtextтекст подсказки к исключению (если есть)
PG_EXCEPTION_CONTEXTtextстрока (или строки) с описанием стека вызова

Если исключение не устанавливает значение для идентификатора, то возвращается пустая строка.

Пример:

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;

40.6.7. Получение информации о выполнении в текущий момент

Команда GET [ CURRENT ] DIAGNOSTICS извлекает информацию о текущем состоянии выполнения (в то время как обсуждавшаяся выше команда GET STACKED DIAGNOSTICS выдаёт информацию о состоянии выполнения на момент предыдущей ошибки). Команда имеет следующий вид:

GET [CURRENT] DIAGNOSTICS variable { = | := } item [, ...];

В настоящий момент поддерживается только один item PG_CONTEXT, который возвращает значение типа text, содержащее стек вызова. Стек вызова может состоять из нескольких строк, первая строка относится к выполняемый в текущий момент команде 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)