×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

При отключении обработки cookie наш сайт продолжит функционировать, однако будут использоваться исключительно необходимые технические файлы, без которых работа ресурса невозможна.

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

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

5.6.6.1. Возврат из функции
5.6.6.2. Возврат управления из процедуры
5.6.6.3. Вызов процедуры
5.6.6.4. Условные конструкции
5.6.6.5. Простые циклы
5.6.6.6. Циклы по результатам запроса
5.6.6.7. Циклы по массивам
5.6.6.8. Перехват ошибок
5.6.6.9. Получение информации о месте выполнения

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

5.6.6.1. Возврат из функции #

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

5.6.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);  -- необходимо привести столбцы к соответствующим типам

5.6.6.1.2. RETURN NEXT и RETURN QUERY #

RETURN NEXT выражение;
RETURN QUERY запрос;
RETURN QUERY EXECUTE строка-команды [ USING выражение [, ... ] ];

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

5.6.6.2. Возврат управления из процедуры #

Процедура не имеет возвращаемого значения. Следовательно, процедура может завершиться без использования оператора RETURN RETURN. Для использования инструкции RETURN в целях досрочного выхода из кода следует указать только RETURN без выражения.

Если процедура имеет выходные параметры, вызывающей стороне будут возвращены конечные значения соответствующих переменных.

5.6.6.3. Вызов процедуры #

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;
$$;

Переменная, соответствующая выходному параметру, может быть простой переменной или полем переменной составного типа. В настоящее время она не может быть элементом массива.

5.6.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

5.6.6.4.1. IF-THEN #

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

5.6.6.4.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;

5.6.6.4.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 условия не проверяются.) Если ни одно из 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 в тех случаях, когда имеется много альтернатив.

5.6.6.4.4. Простой CASE #

CASE search-expression
    WHEN выражение [, выражение [ ... ]] THEN
      statements
  [ WHEN выражение [, выражение [ ... ]] THEN
      statements
    ... ]
  [ ELSE
      statements ]
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;

5.6.6.4.5. Поисковый 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 предложения приводит к ошибке, а не к отсутствию каких-либо действий.

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

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

5.6.6.5.1. LOOP #

[ <<label>> ]
LOOP
    statements
END LOOP [ label ];

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

5.6.6.5.2. EXIT #

EXIT [ label ] [ WHEN boolean-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;

5.6.6.5.3. CONTINUE #

CONTINUE [ label ] [ WHEN boolean-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;

5.6.6.5.4. WHILE #

[ <<label>> ]
WHILE boolean-expression LOOP
    statements
END 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;

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

[ <<label>> ]
FOR имя IN [ REVERSE ] выражение .. выражение [ BY выражение ] LOOP
    statements
END 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.

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

Используя другой тип FOR цикла, можно итерировать результаты запроса и соответствующим образом обрабатывать эти данные. Синтаксис следующий:

[ <<label>> ]
FOR target IN запрос LOOP
    statements
END 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>> ]
FOR target IN EXECUTE text_expression [ USING выражение [, ... ] ] LOOP
    statements
END LOOP [ label ];

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

Еще один способ определить запрос, по результатам которого должна выполняться итерация, заключается в его объявлении в виде курсора. Это описано в разделе Раздел 5.6.7.4.

5.6.6.7. Циклы по массивам #

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

[ <<label>> ]
FOREACH target [ SLICE число ] IN ARRAY выражение LOOP
    statements
END 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}

5.6.6.8. Перехват ошибок #

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

[ <<label>> ]
[ DECLARE
    объявления ]
BEGIN
    statements
EXCEPTION
    WHEN условие [ OR условие ... ] THEN
        handler_statements
    [ WHEN условие [ OR условие ... ] THEN
          handler_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 в триггерной функции для таблицы. Код также может работать некорректно при наличии нескольких уникальных индексов для таблицы, так как операция будет повторена независимо от того, какой именно индекс вызвал ошибку. Повысить уровень надежности можно с помощью рассматриваемых далее возможностей для проверки того, что перехваченная ошибка соответствует ожидаемой.


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

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

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

Внутри обработчика исключения также можно получить информацию о текущем исключении, используя команду GET STACKED DIAGNOSTICS команду следующего формата:

GET STACKED DIAGNOSTICS переменная { = | := } элемент [ , ... ];

Каждый элемент — это ключевое слово, определяющее значение статуса, которое будет присвоено указанному параметру переменная (который должен иметь подходящий тип данных для получения этого значения). Доступные в настоящее время элементы состояния приведены в Таблица 5.6.2.

Таблица 5.6.2. Элементы диагностики ошибок

ИмяТипОписание
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строки текста, описывающие стек вызовов в момент возникновения исключение (см. Раздел 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;

5.6.6.9. Получение информации о месте выполнения #

Синтаксис 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 возвращает аналогичную трассировку стека, но описывает место, в котором была обнаружена ошибка, а не текущее местоположение.

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению