Error query returned no rows

43.5. Basic Statements 43.5.1. Assignment 43.5.2. Executing SQL Commands 43.5.3. Executing a Command with a Single-Row Result 43.5.4. Executing Dynamic Commands …

In this section and the following ones, we describe all the statement types that are explicitly understood by PL/pgSQL. Anything not recognized as one of these statement types is presumed to be an SQL command and is sent to the main database engine to execute, as described in Section 43.5.2.

43.5.1. Assignment

An assignment of a value to a PL/pgSQL variable is written as:

variable { := | = } expression;

As explained previously, the expression in such a statement is evaluated by means of an SQL SELECT command sent to the main database engine. The expression must yield a single value (possibly a row value, if the variable is a row or record variable). The target variable can be a simple variable (optionally qualified with a block name), a field of a row or record target, or an element or slice of an array target. Equal (=) can be used instead of PL/SQL-compliant :=.

If the expression’s result data type doesn’t match the variable’s data type, the value will be coerced as though by an assignment cast (see Section 10.4). If no assignment cast is known for the pair of data types involved, the PL/pgSQL interpreter will attempt to convert the result value textually, that is by applying the result type’s output function followed by the variable type’s input function. Note that this could result in run-time errors generated by the input function, if the string form of the result value is not acceptable to the input function.

Examples:

tax := subtotal * 0.06;
my_record.user_id := 20;
my_array[j] := 20;
my_array[1:3] := array[1,2,3];
complex_array[n].realpart = 12.3;

43.5.2. Executing SQL Commands

In general, any SQL command that does not return rows can be executed within a PL/pgSQL function just by writing the command. For example, you could create and fill a table by writing

CREATE TABLE mytable (id int primary key, data text);
INSERT INTO mytable VALUES (1,'one'), (2,'two');

If the command does return rows (for example SELECT, or INSERT/UPDATE/DELETE with RETURNING), there are two ways to proceed. When the command will return at most one row, or you only care about the first row of output, write the command as usual but add an INTO clause to capture the output, as described in Section 43.5.3. To process all of the output rows, write the command as the data source for a FOR loop, as described in Section 43.6.6.

Usually it is not sufficient just to execute statically-defined SQL commands. Typically you’ll want a command to use varying data values, or even to vary in more fundamental ways such as by using different table names at different times. Again, there are two ways to proceed depending on the situation.

PL/pgSQL variable values can be automatically inserted into optimizable SQL commands, which are SELECT, INSERT, UPDATE, DELETE, and certain utility commands that incorporate one of these, such as EXPLAIN and CREATE TABLE ... AS SELECT. In these commands, any PL/pgSQL variable name appearing in the command text is replaced by a query parameter, and then the current value of the variable is provided as the parameter value at run time. This is exactly like the processing described earlier for expressions; for details see Section 43.11.1.

When executing an optimizable SQL command in this way, PL/pgSQL may cache and re-use the execution plan for the command, as discussed in Section 43.11.2.

Non-optimizable SQL commands (also called utility commands) are not capable of accepting query parameters. So automatic substitution of PL/pgSQL variables does not work in such commands. To include non-constant text in a utility command executed from PL/pgSQL, you must build the utility command as a string and then EXECUTE it, as discussed in Section 43.5.4.

EXECUTE must also be used if you want to modify the command in some other way than supplying a data value, for example by changing a table name.

Sometimes it is useful to evaluate an expression or SELECT query but discard the result, for example when calling a function that has side-effects but no useful result value. To do this in PL/pgSQL, use the PERFORM statement:

PERFORM query;

This executes query and discards the result. Write the query the same way you would write an SQL SELECT command, but replace the initial keyword SELECT with PERFORM. For WITH queries, use PERFORM and then place the query in parentheses. (In this case, the query can only return one row.) PL/pgSQL variables will be substituted into the query just as described above, and the plan is cached in the same way. Also, the special variable FOUND is set to true if the query produced at least one row, or false if it produced no rows (see Section 43.5.5).

Note

One might expect that writing SELECT directly would accomplish this result, but at present the only accepted way to do it is PERFORM. An SQL command that can return rows, such as SELECT, will be rejected as an error unless it has an INTO clause as discussed in the next section.

An example:

PERFORM create_mv('cs_session_page_requests_mv', my_query);

43.5.3. Executing a Command with a Single-Row Result

The result of an SQL command yielding a single row (possibly of multiple columns) can be assigned to a record variable, row-type variable, or list of scalar variables. This is done by writing the base SQL command and adding an INTO clause. For example,

SELECT select_expressions INTO [STRICT] target FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] target;
UPDATE ... RETURNING expressions INTO [STRICT] target;
DELETE ... RETURNING expressions INTO [STRICT] target;

where target can be a record variable, a row variable, or a comma-separated list of simple variables and record/row fields. PL/pgSQL variables will be substituted into the rest of the command (that is, everything but the INTO clause) just as described above, and the plan is cached in the same way. This works for SELECT, INSERT/UPDATE/DELETE with RETURNING, and certain utility commands that return row sets, such as EXPLAIN. Except for the INTO clause, the SQL command is the same as it would be written outside PL/pgSQL.

Tip

Note that this interpretation of SELECT with INTO is quite different from PostgreSQL‘s regular SELECT INTO command, wherein the INTO target is a newly created table. If you want to create a table from a SELECT result inside a PL/pgSQL function, use the syntax CREATE TABLE ... AS SELECT.

If a row variable or a variable list is used as target, the command’s result columns must exactly match the structure of the target as to number and data types, or else a run-time error occurs. When a record variable is the target, it automatically configures itself to the row type of the command’s result columns.

The INTO clause can appear almost anywhere in the SQL command. Customarily it is written either just before or just after the list of select_expressions in a SELECT command, or at the end of the command for other command types. It is recommended that you follow this convention in case the PL/pgSQL parser becomes stricter in future versions.

If STRICT is not specified in the INTO clause, then target will be set to the first row returned by the command, or to nulls if the command returned no rows. (Note that the first row is not well-defined unless you’ve used ORDER BY.) Any result rows after the first row are discarded. You can check the special FOUND variable (see Section 43.5.5) to determine whether a row was returned:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employee % not found', myname;
END IF;

If the STRICT option is specified, the command must return exactly one row or a run-time error will be reported, either NO_DATA_FOUND (no rows) or TOO_MANY_ROWS (more than one row). You can use an exception block if you wish to catch the error, for example:

BEGIN
    SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE EXCEPTION 'employee % not found', myname;
        WHEN TOO_MANY_ROWS THEN
            RAISE EXCEPTION 'employee % not unique', myname;
END;

Successful execution of a command with STRICT always sets FOUND to true.

For INSERT/UPDATE/DELETE with RETURNING, PL/pgSQL reports an error for more than one returned row, even when STRICT is not specified. This is because there is no option such as ORDER BY with which to determine which affected row should be returned.

If print_strict_params is enabled for the function, then when an error is thrown because the requirements of STRICT are not met, the DETAIL part of the error message will include information about the parameters passed to the command. You can change the print_strict_params setting for all functions by setting plpgsql.print_strict_params, though only subsequent function compilations will be affected. You can also enable it on a per-function basis by using a compiler option, for example:

CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE
userid int;
BEGIN
    SELECT users.userid INTO STRICT userid
        FROM users WHERE users.username = get_userid.username;
    RETURN userid;
END;
$$ LANGUAGE plpgsql;

On failure, this function might produce an error message such as

ERROR:  query returned no rows
DETAIL:  parameters: $1 = 'nosuchuser'
CONTEXT:  PL/pgSQL function get_userid(text) line 6 at SQL statement

Note

The STRICT option matches the behavior of Oracle PL/SQL’s SELECT INTO and related statements.

43.5.4. Executing Dynamic Commands

Oftentimes you will want to generate dynamic commands inside your PL/pgSQL functions, that is, commands that will involve different tables or different data types each time they are executed. PL/pgSQL‘s normal attempts to cache plans for commands (as discussed in Section 43.11.2) will not work in such scenarios. To handle this sort of problem, the EXECUTE statement is provided:

EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];

where command-string is an expression yielding a string (of type text) containing the command to be executed. The optional target is a record variable, a row variable, or a comma-separated list of simple variables and record/row fields, into which the results of the command will be stored. The optional USING expressions supply values to be inserted into the command.

No substitution of PL/pgSQL variables is done on the computed command string. Any required variable values must be inserted in the command string as it is constructed; or you can use parameters as described below.

Also, there is no plan caching for commands executed via EXECUTE. Instead, the command is always planned each time the statement is run. Thus the command string can be dynamically created within the function to perform actions on different tables and columns.

The INTO clause specifies where the results of an SQL command returning rows should be assigned. If a row variable or variable list is provided, it must exactly match the structure of the command’s results; if a record variable is provided, it will configure itself to match the result structure automatically. If multiple rows are returned, only the first will be assigned to the INTO variable(s). If no rows are returned, NULL is assigned to the INTO variable(s). If no INTO clause is specified, the command results are discarded.

If the STRICT option is given, an error is reported unless the command produces exactly one row.

The command string can use parameter values, which are referenced in the command as $1, $2, etc. These symbols refer to values supplied in the USING clause. This method is often preferable to inserting data values into the command string as text: it avoids run-time overhead of converting the values to text and back, and it is much less prone to SQL-injection attacks since there is no need for quoting or escaping. An example is:

EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

Note that parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually. For example, if the preceding query needed to be done against a dynamically selected table, you could do this:

EXECUTE 'SELECT count(*) FROM '
    || quote_ident(tabname)
    || ' WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

A cleaner approach is to use format()‘s %I specification to insert table or column names with automatic quoting:

EXECUTE format('SELECT count(*) FROM %I '
   'WHERE inserted_by = $1 AND inserted <= $2', tabname)
   INTO c
   USING checked_user, checked_date;

(This example relies on the SQL rule that string literals separated by a newline are implicitly concatenated.)

Another restriction on parameter symbols is that they only work in optimizable SQL commands (SELECT, INSERT, UPDATE, DELETE, MERGE, and certain commands containing one of these). In other statement types (generically called utility statements), you must insert values textually even if they are just data values.

An EXECUTE with a simple constant command string and some USING parameters, as in the first example above, is functionally equivalent to just writing the command directly in PL/pgSQL and allowing replacement of PL/pgSQL variables to happen automatically. The important difference is that EXECUTE will re-plan the command on each execution, generating a plan that is specific to the current parameter values; whereas PL/pgSQL may otherwise create a generic plan and cache it for re-use. In situations where the best plan depends strongly on the parameter values, it can be helpful to use EXECUTE to positively ensure that a generic plan is not selected.

SELECT INTO is not currently supported within EXECUTE; instead, execute a plain SELECT command and specify INTO as part of the EXECUTE itself.

Note

The PL/pgSQL EXECUTE statement is not related to the EXECUTE SQL statement supported by the PostgreSQL server. The server’s EXECUTE statement cannot be used directly within PL/pgSQL functions (and is not needed).

Example 43.1. Quoting Values in Dynamic Queries

When working with dynamic commands you will often have to handle escaping of single quotes. The recommended method for quoting fixed text in your function body is dollar quoting. (If you have legacy code that does not use dollar quoting, please refer to the overview in Section 43.12.1, which can save you some effort when translating said code to a more reasonable scheme.)

Dynamic values require careful handling since they might contain quote characters. An example using format() (this assumes that you are dollar quoting the function body so quote marks need not be doubled):

EXECUTE format('UPDATE tbl SET %I = $1 '
   'WHERE key = $2', colname) USING newvalue, keyvalue;

It is also possible to call the quoting functions directly:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_literal(newvalue)
        || ' WHERE key = '
        || quote_literal(keyvalue);

This example demonstrates the use of the quote_ident and quote_literal functions (see Section 9.4). For safety, expressions containing column or table identifiers should be passed through quote_ident before insertion in a dynamic query. Expressions containing values that should be literal strings in the constructed command should be passed through quote_literal. These functions take the appropriate steps to return the input text enclosed in double or single quotes respectively, with any embedded special characters properly escaped.

Because quote_literal is labeled STRICT, it will always return null when called with a null argument. In the above example, if newvalue or keyvalue were null, the entire dynamic query string would become null, leading to an error from EXECUTE. You can avoid this problem by using the quote_nullable function, which works the same as quote_literal except that when called with a null argument it returns the string NULL. For example,

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_nullable(newvalue)
        || ' WHERE key = '
        || quote_nullable(keyvalue);

If you are dealing with values that might be null, you should usually use quote_nullable in place of quote_literal.

As always, care must be taken to ensure that null values in a query do not deliver unintended results. For example the WHERE clause

'WHERE key = ' || quote_nullable(keyvalue)

will never succeed if keyvalue is null, because the result of using the equality operator = with a null operand is always null. If you wish null to work like an ordinary key value, you would need to rewrite the above as

'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)

(At present, IS NOT DISTINCT FROM is handled much less efficiently than =, so don’t do this unless you must. See Section 9.2 for more information on nulls and IS DISTINCT.)

Note that dollar quoting is only useful for quoting fixed text. It would be a very bad idea to try to write this example as:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = $$'
        || newvalue
        || '$$ WHERE key = '
        || quote_literal(keyvalue);

because it would break if the contents of newvalue happened to contain $$. The same objection would apply to any other dollar-quoting delimiter you might pick. So, to safely quote text that is not known in advance, you must use quote_literal, quote_nullable, or quote_ident, as appropriate.

Dynamic SQL statements can also be safely constructed using the format function (see Section 9.4.1). For example:

EXECUTE format('UPDATE tbl SET %I = %L '
   'WHERE key = %L', colname, newvalue, keyvalue);

%I is equivalent to quote_ident, and %L is equivalent to quote_nullable. The format function can be used in conjunction with the USING clause:

EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
   USING newvalue, keyvalue;

This form is better because the variables are handled in their native data type format, rather than unconditionally converting them to text and quoting them via %L. It is also more efficient.

A much larger example of a dynamic command and EXECUTE can be seen in Example 43.10, which builds and executes a CREATE FUNCTION command to define a new function.

43.5.5. Obtaining the Result Status

There are several ways to determine the effect of a command. The first method is to use the GET DIAGNOSTICS command, which has the form:

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

This command allows retrieval of system status indicators. CURRENT is a noise word (but see also GET STACKED DIAGNOSTICS in Section 43.6.8.1). Each item is a key word identifying a status value to be assigned to the specified variable (which should be of the right data type to receive it). The currently available status items are shown in Table 43.1. Colon-equal (:=) can be used instead of the SQL-standard = token. An example:

GET DIAGNOSTICS integer_var = ROW_COUNT;

Table 43.1. Available Diagnostics Items

Name Type Description
ROW_COUNT bigint the number of rows processed by the most recent SQL command
PG_CONTEXT text line(s) of text describing the current call stack (see Section 43.6.9)

The second method to determine the effects of a command is to check the special variable named FOUND, which is of type boolean. FOUND starts out false within each PL/pgSQL function call. It is set by each of the following types of statements:

  • A SELECT INTO statement sets FOUND true if a row is assigned, false if no row is returned.

  • A PERFORM statement sets FOUND true if it produces (and discards) one or more rows, false if no row is produced.

  • UPDATE, INSERT, DELETE, and MERGE statements set FOUND true if at least one row is affected, false if no row is affected.

  • A FETCH statement sets FOUND true if it returns a row, false if no row is returned.

  • A MOVE statement sets FOUND true if it successfully repositions the cursor, false otherwise.

  • A FOR or FOREACH statement sets FOUND true if it iterates one or more times, else false. FOUND is set this way when the loop exits; inside the execution of the loop, FOUND is not modified by the loop statement, although it might be changed by the execution of other statements within the loop body.

  • RETURN QUERY and RETURN QUERY EXECUTE statements set FOUND true if the query returns at least one row, false if no row is returned.

Other PL/pgSQL statements do not change the state of FOUND. Note in particular that EXECUTE changes the output of GET DIAGNOSTICS, but does not change FOUND.

FOUND is a local variable within each PL/pgSQL function; any changes to it affect only the current function.

43.5.6. Doing Nothing At All

Sometimes a placeholder statement that does nothing is useful. For example, it can indicate that one arm of an if/then/else chain is deliberately empty. For this purpose, use the NULL statement:

NULL;

For example, the following two fragments of code are equivalent:

BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        NULL;  -- ignore the error
END;
BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN  -- ignore the error
END;

Which is preferable is a matter of taste.

Note

In Oracle’s PL/SQL, empty statement lists are not allowed, and so NULL statements are required for situations such as this. PL/pgSQL allows you to just write nothing, instead.

Summary: in this tutorial, you will learn how to catch PostgreSQL exceptions in PL/pgSQL.

Introduction to the PL/pgSQL Exception clause

When an error occurs in a block, PostgreSQL will abort the execution of the block and also the surrounding transaction.

To recover from the error, you can use the exception clause in the begin...end block.

The following illustrates the syntax of the exception clause:

<<label>> declare begin statements; exception when condition [or condition...] then handle_exception; [when condition [or condition...] then handle_exception;] [when others then handle_other_exceptions; ] end;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

How it works.

  • First, when an error occurs between the begin and exception, PL/pgSQL stops the execution and passes the control to the exception list.
  • Second, PL/pgSQL searches for the first condition that matches the occurring error.
  • Third, if there is a match, the corresponding handle_exception statements will execute. PL/pgSQL passes the control to the statement after the end keyword.
  • Finally, if no match found, the error propagates out and can be caught by the exception clause of the enclosing block. In case there is no enclosing block with the exception clause, PL/pgSQL will abort the processing.

The condition names can be no_data_found in case of a select statement return no rows or too_many_rows if the select statement returns more than one row. For a complete list of condition names on the PostgreSQL website.

It’s also possible to specify the error condition by SQLSTATE code. For example, P0002 for no_data_found and P0003 for too_many_rows.

Typically, you will catch a specific exception and handle it accordingly. To handle other exceptions rather than the one you specify on the list, you can use the when others then clause.

Handling exception examples

We’ll use the film table from the sample database for the demonstration.

Film table

1) Handling no_data_found exception example

The following example issues an error because the film with id 2000 does not exist.

do $$ declare rec record; v_film_id int = 2000; begin -- select a film select film_id, title into strict rec from film where film_id = v_film_id; end; $$ language plpgsql;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

Output:

ERROR: query returned no rows CONTEXT: PL/pgSQL function inline_code_block line 6 at SQL statement SQL state: P0002

Code language: Shell Session (shell)

The following example uses the exception clause to catch the no_data_found exception and report a more meaningful message:

do $$ declare rec record; v_film_id int = 2000; begin -- select a film select film_id, title into strict rec from film where film_id = v_film_id; -- catch exception exception when no_data_found then raise exception 'film % not found', v_film_id; end; $$ language plpgsql;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

Output:

ERROR: film 2000 not found CONTEXT: PL/pgSQL function inline_code_block line 14 at RAISE SQL state: P0001

Code language: Shell Session (shell)

2) Handling too_many_rows exception example

The following example illustrates how to handle the too_many_rows exception:

do $$ declare rec record; begin -- select film select film_id, title into strict rec from film where title LIKE 'A%'; exception when too_many_rows then raise exception 'Search query returns too many rows'; end; $$ language plpgsql;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

Output:

ERROR: Search query returns too many rows CONTEXT: PL/pgSQL function inline_code_block line 15 at RAISE SQL state: P0001

Code language: Shell Session (shell)

In this example, the too_many_rows exception occurs because the select into statement returns more than one row while it is supposed to return one row.

3) Handling multiple exceptions

The following example illustrates how to catch multiple exceptions:

do $$ declare rec record; v_length int = 90; begin -- select a film select film_id, title into strict rec from film where length = v_length; -- catch exception exception when sqlstate 'P0002' then raise exception 'film with length % not found', v_length; when sqlstate 'P0003' then raise exception 'The with length % is not unique', v_length; end; $$ language plpgsql;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

Output:

ERROR: The with length 90 is not unique CONTEXT: PL/pgSQL function inline_code_block line 17 at RAISE SQL state: P0001

Code language: Shell Session (shell)

4) Handling exceptions as SQLSTATE codes

The following example is the same as the one above except that it uses the SQLSTATE codes instead of the condition names:

do $$ declare rec record; v_length int = 30; begin -- select a film select film_id, title into strict rec from film where length = v_length; -- catch exception exception when sqlstate 'P0002' then raise exception 'film with length % not found', v_length; when sqlstate 'P0003' then raise exception 'The with length % is not unique', v_length; end; $$ language plpgsql;

Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)

Output:

ERROR: film with length 30 not found CONTEXT: PL/pgSQL function inline_code_block line 15 at RAISE SQL state: P0001

Code language: Shell Session (shell)

Summary

  • Use the exception clause in the begin...end block to catch and handle exceptions.

Was this tutorial helpful ?

Hello,

I’ve found this issue:

timescale=# truncate table values_v2;
ERROR: query returned no rows
CONTEXT: PL/pgSQL function _timescaledb_internal.drop_chunk_constraint(integer,name,boolean) line 14 at SQL statement
SQL statement «SELECT _timescaledb_internal.drop_chunk_constraint(cc.chunk_id, cc.constraint_name, false)
FROM _timescaledb_catalog.chunk_constraint cc
WHERE cc.chunk_id = drop_chunk_metadata.chunk_id»
PL/pgSQL function _timescaledb_internal.drop_chunk_metadata(integer) line 11 at PERFORM
SQL statement «
DROP TABLE _timescaledb_internal._hyper_4_6_chunk
«
PL/pgSQL function drop_chunks_impl(bigint,name,name,boolean,boolean) line 47 at EXECUTE
SQL statement «SELECT _timescaledb_internal.drop_chunks_impl(NULL, table_name, schema_name, cascade, true)»
PL/pgSQL function truncate_hypertable(name,name,boolean) line 7 at PERFORM

drop table fails too:

timescale=# drop table values_v2 cascade;
ERROR: query returned no rows
CONTEXT: PL/pgSQL function _timescaledb_internal.drop_chunk_constraint(integer,name,boolean) line 14 at SQL statement
SQL statement «SELECT _timescaledb_internal.drop_chunk_constraint(cc.chunk_id, cc.constraint_name, false)
FROM _timescaledb_catalog.chunk_constraint cc
WHERE cc.chunk_id = drop_chunk_metadata.chunk_id»
PL/pgSQL function _timescaledb_internal.drop_chunk_metadata(integer) line 11 at PERFORM
SQL statement «
DROP TABLE _timescaledb_internal._hyper_4_6_chunk CASCADE
«
PL/pgSQL function _timescaledb_internal.drop_hypertable(integer,boolean) line 14 at EXECUTE

timescale=# d+ values_v2;
Table «public.values_v2»
Column | Type | Modifiers | Storage | Stats target | Description
————+———+————+———-+—————+————-
metric_id | integer | not null | plain | |
time | bigint | not null | plain | |
values | json | not null | extended | |
Indexes:
«values_v2_metric_id_time_idx» btree (metric_id, «time» DESC)
«values_v2_time_idx» btree («time» DESC)
Child tables: _timescaledb_internal._hyper_4_6_chunk,
_timescaledb_internal._hyper_4_7_chunk

rpm -q timescaledb
timescaledb-0.8.0-0.el7.centos.x86_64

Документация по СУБД «Квант-Гибрид» 1.5.0

PL/pgSQL — процедурный язык SQL

  • Обзор
    • Преимущества использования PL/pgSQL
    • Поддерживаемые типы данных аргумента и результата
  • Структура PL/pgSQL
  • Объявления
    • Объявление параметров функции
    • ALIAS
    • Типы копирования
    • Типы строк
    • Типы записей
    • Сортировка переменных PL/pgSQL
  • Выражения
  • Основные положения
    • Присваивание
    • Выполнение команды без результата
    • Выполнение запроса с результатом в одну строку
    • Выполнение динамических команд
    • Получение статуса результата
    • Пустой оператор
  • Управляющие структуры
    • Возврат из функции
      • RETURN
      • RETURN NEXT и RETURN QUERY
    • Возврат из процедуры
    • Вызов процедуры
    • Условные операторы
      • IF-THEN
      • IF-THEN-ELSE
      • IF-THEN-ELSIF
      • Простой CASE
      • Поисковое выражение CASE
    • Простые циклы
      • LOOP
      • EXIT
      • CONTINUE
      • WHILE
      • FOR (целочисленный вариант)
    • Цикл по результатам запроса
    • Цикл по массивам
    • Ошибки захвата
      • Получение информации об ошибке
    • Получение информации о месте выполнения
  • Курсоры
    • Объявление переменных курсора
    • Открытие курсоров
      • OPEN FOR query
      • OPEN FOR EXECUTE
      • Открытие связанного курсора
    • Использование курсоров
      • FETCH
      • MOVE
      • UPDATE/DELETE WHERE CURRENT OF
      • CLOSE
      • Возврат курсора
    • Цикл по результату курсора
  • Управление транзакциями
  • Ошибки и сообщения
    • Вывод ошибок и сообщений
    • Проверка утверждений
  • Триггерные функции
    • Триггеры при изменении данных
    • Триггеры на события
  • PL/pgSQL под капотом
    • Подстановка переменных
    • Планирование кэширования
  • Советы по разработке на PL/pgSQL
    • Обработка кавычек
    • Дополнительные проверки во время компиляции и во время выполнения
  • Портирование из Oracle PL/SQL
    • Примеры портирования
    • Другие вещи для наблюдения
      • Неявный откат после исключений
      • EXECUTE
      • Оптимизация функций PL/pgSQL
    • Приложение

Обзор

PL/pgSQL — это загружаемый процедурный язык для системы баз данных
QHB. Цели разработки PL/pgSQL заключались в создании
загружаемого процедурного языка, который

  • может быть использован для создания функций и триггеров,
  • добавляет структуры управления к языку SQL,
  • может выполнять сложные вычисления,
  • наследует все пользовательские типы, функции и операторы,
  • может быть определен как доверенный сервером,
  • прост в использовании.

Функции, созданные с помощью PL/pgSQL, можно использовать везде, где
могут использоваться встроенные функции. Например, можно создавать
сложные функции условного вычисления, а затем использовать их для
определения операторов или использовать их в выражениях индекса.

В QHB PL/pgSQL устанавливается по
умолчанию. Однако это все еще загружаемый модуль, поэтому
администраторы, особенно заботящиеся о безопасности, могут удалить его.

Преимущества использования PL/pgSQL

SQL — это язык QHB, и большинство других реляционных баз данных
используют его в качестве языка запросов. Это портативный и легкий в
освоении. Но каждый оператор SQL должен выполняться сервером базы данных
индивидуально.

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

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

  • Дополнительные обходы между клиентом и сервером исключены
  • Промежуточные результаты, которые не нужны клиенту, не нужно
    передавать между сервером и клиентом
  • Можно избежать нескольких раундов разбора запроса

Это может привести к значительному увеличению производительности по
сравнению с приложением, которое не использует хранимые функции.

Кроме того, с PL/pgSQL вы можете использовать все типы данных,
операторы и функции SQL.

Поддерживаемые типы данных аргумента и результата

Функции, написанные на PL/pgSQL, могут принимать в качестве аргументов
любой скалярный или массив данных, поддерживаемый сервером, и они могут
возвращать результат любого из этих типов. Они также могут принимать или
возвращать любой составной тип (тип строки), указанный именем. Также
возможно объявить функцию PL/pgSQL как принимающую record, что
означает, что любой составной тип будет делать как ввод, или как
возвращающую record, что означает, что результатом является тип строки,
столбцы которой определяются спецификацией в вызывающем запросе, как
описано в разделе Табличные функции.

Функции PL/pgSQL могут быть объявлены для приема переменного числа
аргументов с помощью маркера VARIADIC. Это работает точно так же, как и
для функций SQL, как описано в разделе Функции SQL с переменным числом аргументов.

Функции PL/pgSQL также могут быть объявлены для приема и возврата
полиморфных типов anyelement, anyarray, anynonarray, anyenum и
anyrange. Фактические типы данных, обрабатываемые полиморфной функцией,
могут варьироваться от вызова к вызову, как описано в разделе Полиморфные типы.
Пример показан в разделе Объявление параметров функции.

Функции PL/pgSQL также могут быть объявлены так, чтобы они возвращали
«набор» (или таблицу) любого типа данных, который может быть возвращен
как один экземпляр. Такая функция генерирует свои выходные данные,
выполняя RETURN NEXT для каждого требуемого элемента набора результатов
или используя RETURN QUERY для вывода результата оценки запроса.

Наконец, можно объявить функцию PL/pgSQL, которая возвращает void если
она не имеет полезного возвращаемого значения. (В качестве альтернативы,
это может быть написано как процедура в этом случае).

Функции PL/pgSQL также могут быть объявлены с выходными параметрами
вместо явной спецификации возвращаемого типа. Это не добавляет
фундаментальной возможности к языку, но это часто удобно, особенно для
возврата нескольких значений. Обозначение RETURNS TABLE также может
использоваться вместо RETURNS SETOF.

Конкретные примеры приведены в разделе Объявление параметров функции и разделе Возврат из функции.

Структура PL/pgSQL

Функции, написанные на PL/pgSQL, определяются сервером путем
выполнения команд CREATE FUNCTION. Такая команда обычно выглядит,
скажем,

CREATE FUNCTION somefunc(integer, text) RETURNS integer
AS 'function body text'
LANGUAGE plpgsql;

Тело функции — это просто строковый литерал в отношении CREATE FUNCTION.
Часто для написания тела функции полезно использовать знаки доллара (см. раздел
Строковые константы с экранированием знаками доллара), а не обычный синтаксис с
одинарными кавычками. Без использования знаков доллара любые одиночные кавычки или
обратные черты в теле функции должны быть экранированы путем их удвоения.
Почти все примеры в этой главе используют литералы в кавычках для своих
функций.

PL/pgSQL — это блочно-структурированный язык. Полный текст тела
функции должен быть блоком. Блок определяется как:

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
END [ label ];

Каждое объявление и каждый оператор в блоке заканчиваются точкой с
запятой. Блок, который появляется внутри другого блока, должен иметь
точку с запятой после END, как показано выше; однако последний END
который завершает тело функции, не требует точки с запятой.

Заметка
Распространенная ошибка — писать точку с запятой сразу после BEGIN. Это
неверно и приведет к синтаксической ошибке.

label нужна только в том случае, если вы хотите идентифицировать блок
для использования в операторе EXIT или указать имена переменных,
объявленных в блоке. Если метка дается после END, она должна совпадать
с меткой в начале блока.

Все ключевые слова не чувствительны к регистру. Идентификаторы неявно
преобразуются в нижний регистр, если они не заключены в двойные кавычки,
как в обычных командах SQL.

Комментарии работают в коде PL/pgSQL так же, как и в обычном SQL.
Двойная черта ( — ) начинает комментарий, который простирается до конца
строки. /* запускает комментарий блока, который распространяется до
появления символов */.

Любой оператор в разделе операторов блока может быть субблоком.
Подблоки могут использоваться для логической группировки или для
локализации переменных в небольшой группе операторов. Переменные,
объявленные в подблоке, маскируют любые переменные с одинаковыми именами
внешних блоков на время действия субблока; но вы все равно можете
получить доступ к внешним переменным, если укажете их имена с меткой их
блока. Например:

CREATE FUNCTION somefunc() RETURNS integer AS $$
<< outerblock >>
DECLARE
    quantity integer := 30;
BEGIN
    RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 30
    quantity := 50;
    --
    -- Create a subblock
    --
    DECLARE
        quantity integer := 80;
    BEGIN
        RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 80
        RAISE NOTICE 'Outer quantity here is %', outerblock.quantity;  -- Prints 50
    END;

    RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 50

    RETURN quantity;
END;
$$ LANGUAGE plpgsql;

Заметка
На самом деле существует скрытый «внешний блок», окружающий тело любой
функции PL/pgSQL. Этот блок предоставляет объявления параметров
функции (если есть), а также некоторые специальные переменные, такие как
FOUND (см. раздел Получение статуса результата). Внешний блок помечен именем функции, что
означает, что параметры и специальные переменные могут быть дополнены
именем функции.

Важно не путать использование BEGIN/END для группировки операторов в
PL/pgSQL с одноименными командами SQL для управления транзакциями.
PL/pgSQL BEGIN/END предназначены только для группировки; они не
начинают и не заканчивают транзакцию. См. раздел Управление транзакциями для получения
информации об управлении транзакциями в PL/pgSQL. Кроме того, блок,
содержащий предложение EXCEPTION, эффективно формирует субтранзакцию,
которую можно откатить без влияния на внешнюю транзакцию. Подробнее об
этом см. раздел Ошибки захвата.

Объявления

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

Переменные PL/pgSQL могут иметь любой тип данных SQL, например,
integer, varchar и char.

Вот несколько примеров объявлений переменных:

user_id integer;
quantity numeric(5);
url varchar;
myrow tablename%ROWTYPE;
myfield tablename.columnname%TYPE;
arow RECORD;

Общий синтаксис объявления переменной:

name [ CONSTANT ] type [ COLLATE collation_name ] [ NOT NULL ] [ { DEFAULT | := | = } expression ];

Предложение DEFAULT, если оно задано, задает начальное значение,
назначенное переменной при вводе блока. Если предложение DEFAULT не
задано, переменная инициализируется нулевым значением SQL. Опция
CONSTANT предотвращает присвоение переменной после инициализации, так
что ее значение будет оставаться постоянным на протяжении всего блока.
Опция COLLATE указывает параметры сортировки, которые следует
использовать для переменной (см. раздел Сортировка переменных PL/pgSQL). Если указано NOT NULL,
присвоение нулевого значения приводит к ошибке во время выполнения.
Все переменные, объявленные как NOT NULL должны иметь ненулевое значение
по умолчанию. Равно ( = ) может использоваться вместо
PL/SQL-совместимого :=.

Значение переменной по умолчанию оценивается и присваивается переменной
каждый раз, когда вводится блок (не только один раз за вызов функции).
Так, например, присвоение now() переменной типа timestamp приводит к
тому, что переменная будет иметь время текущего вызова функции, а не
время, когда функция была предварительно скомпилирована.

Примеры:

quantity integer DEFAULT 32;
url varchar := 'http://mysite.com';
user_id CONSTANT integer := 10;

Объявление параметров функции

Параметры, передаваемые в функции, именуются с помощью идентификаторов
$1, $2 и т.д. При желании псевдонимы могут быть объявлены для $n
имен параметров с целью повышения читабельности. Затем можно использовать
псевдоним или числовой идентификатор для ссылки на значение параметра.

Есть два способа создать псевдоним. Предпочтительным способом является
присвоение имени параметру в команде CREATE FUNCTION, например:

CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Другой способ — явно объявить псевдоним, используя синтаксис объявления.

name ALIAS FOR $n;

Тот же пример в этом стиле выглядит так:

CREATE FUNCTION sales_tax(real) RETURNS real AS $$
DECLARE
    subtotal ALIAS FOR $1;
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Заметка
Эти два примера не являются полностью эквивалентными. В первом случае
на промежуточный итог можно ссылаться как на sales_tax.subtotal,
но во втором случае это невозможно. (Если бы мы прикрепили метку к
внутреннему блоку, subtotal можно было бы квалифицировать с этой
меткой).

Еще несколько примеров:

CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- some computations using v_string and index here
END;
$$ LANGUAGE plpgsql;


CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

Когда функция PL/pgSQL объявляется с выходными параметрами, выходным
параметрам присваиваются имена $n и необязательные псевдонимы точно так
же, как обычные входные параметры. Выходной параметр фактически является
переменной, которая начинается с NULL; это должно быть назначено во
время выполнения функции. Конечное значение параметра — это то, что
возвращается. Например, расчет налога с продаж также может быть сделан
следующим образом:

CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
BEGIN
    tax := subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Обратите внимание, что мы пропустили RETURNS real — мы могли бы включить
его, но это было бы излишним.

Выходные параметры наиболее полезны при возврате нескольких значений.
Тривиальный пример:

CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

Как обсуждалось в разделе Функции SQL с выходными параметрами, это эффективно создает анонимный тип
записи для результатов функции. Если задано предложение RETURNS, оно
должно типа record.

Другой способ объявить функцию PL/pgSQL — использовать RETURNS TABLE,
например:

CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
    RETURN QUERY SELECT s.quantity, s.quantity * s.price FROM sales AS s
                 WHERE s.itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;

Это в точности эквивалентно объявлению одного или нескольких параметров
OUT и указанию RETURNS SETOF sometype.

Когда возвращаемый тип функции PL/pgSQL объявлен как полиморфный тип (
anyelement, anyarray, anynonarray, anyenum или anyrange), создается
специальный параметр $0. Тип данных — это фактический тип возвращаемого
значения функции, выведенный из фактических типов ввода (см. раздел Полиморфные типы).
Это позволяет функции получить доступ к ее фактическому типу
возврата, как показано в разделе Типы копирования. $0 инициализируется нулевым
значением и может быть изменено функцией, поэтому его можно использовать
для хранения возвращаемого значения, если это необходимо, хотя это не
требуется. $0 также может быть присвоен псевдоним. Например, эта функция
работает с любым типом данных, который имеет оператор + :

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
    result ALIAS FOR $0;
BEGIN
    result := v1 + v2 + v3;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

Тот же эффект можно получить, объявив один или несколько выходных
параметров как полиморфные типы. В этом случае специальный параметр $0
не используется; Сами выходные параметры служат той же цели. Например:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
                                 OUT sum anyelement)
AS $$
BEGIN
    sum := v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

ALIAS

newname ALIAS FOR oldname;

Синтаксис ALIAS является более общим, чем предлагается в предыдущем
разделе: вы можете объявить псевдоним для любой переменной, а не только
для параметров функции. Основное практическое использование для этого
состоит в назначении другого имени для переменных с предопределенными
именами, таких как NEW или OLD в функции триггера.

Примеры:

DECLARE
  prior ALIAS FOR old;
  updated ALIAS FOR new;

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

Типы копирования

variable%TYPE

%TYPE предоставляет тип данных переменной или столбца таблицы. Вы можете
использовать это для объявления переменных, которые будут содержать
значения базы данных. Например, предположим, у вас есть столбец с именем
user_id в вашей таблице users. Чтобы объявить переменную с тем же
типом данных, что и users.user_id вы пишете:

user_id users.user_id%TYPE;

Используя %TYPE вам не нужно знать тип данных структуры, на которую вы
ссылаетесь, и, самое главное, если тип данных ссылочного элемента
изменится в будущем (например: вы меняете тип user_id с integer на real),
вам может не потребоваться изменить определение функции.

%TYPE особенно полезен в полиморфных функциях, поскольку типы данных,
необходимые для внутренних переменных, могут меняться от одного вызова к
другому. Подходящие переменные могут быть созданы путем применения %TYPE
к аргументам функции или местозаполнителям результата.

Типы строк

name table_name%ROWTYPE;
name composite_type_name;

Переменная составного типа называется переменной строки (или переменной
типа строки). Такая переменная может содержать целую строку результата
запроса SELECT или FOR, если набор столбцов этого запроса соответствует
объявленному типу переменной. Доступ к отдельным полям значения строки
осуществляется с использованием обычной точечной нотации, например
rowvar.field.

Переменная строки может быть объявлена для того же типа, что и строки
существующей таблицы или представления, используя нотацию table_name%ROWTYPE;
или это может быть объявлено путем указания имени составного
типа. (Поскольку каждая таблица имеет связанный составной тип с одним и
тем же именем, в QHB не имеет значения, пишете ли вы %ROWTYPE или
нет. Но форма с %ROWTYPE более переносима).

Параметрами функции могут быть составные типы (полные строки таблицы). В
этом случае соответствующий идентификатор $n будет переменной строки, и
из него можно выбрать поля, например, $1.user_id.

Вот пример использования составных типов. table1 и table2 являются
существующими таблицами, имеющими по крайней мере упомянутые поля:

CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
DECLARE
    t2_row table2%ROWTYPE;
BEGIN
    SELECT * INTO t2_row FROM table2 WHERE ... ;
    RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
END;
$$ LANGUAGE plpgsql;

SELECT merge_fields(t.*) FROM table1 t WHERE ... ;

Типы записей

name RECORD;

Переменные записи аналогичны переменным типа строки, но не имеют
предопределенной структуры. Они принимают фактическую структуру строк,
назначенных им при выполнении команды SELECT или FOR. Подструктура
переменной записи может меняться каждый раз, когда ей назначается.
Следствием этого является то, что до тех пор, пока переменная записи не
будет впервые назначена, она не имеет подструктуры, и любая попытка
доступа к полю в ней вызовет ошибку во время выполнения.

Обратите внимание, что RECORD не является истинным типом данных, а
только заполнителем. Следует также понимать, что когда объявляется
функция PL/pgSQL, возвращающая record типа, это не совсем то же самое,
что и переменная записи, даже если такая функция может использовать
переменную записи для хранения своего результата. В обоих случаях
фактическая структура строки неизвестна при написании функции, но для
функции, возвращающей record фактическая структура определяется при
разборе вызывающего запроса, тогда как переменная записи может изменить
свою структуру строки на лету.

Сортировка переменных PL/pgSQL

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

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b;
END;
$$ LANGUAGE plpgsql;

SELECT less_than(text_field_1, text_field_2) FROM table1;
SELECT less_than(text_field_1, text_field_2 COLLATE "C") FROM table1;

Первое использование less_than будет использовать общее сравнение
text_field_1 и text_field_2 для сравнения, в то время как второе
использование будет использовать C сортировку.

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

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
DECLARE
    local_a text := a;
    local_b text := b;
BEGIN
    RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;

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

Локальная переменная типа данных с возможностью сопоставления может
иметь другой сопоставление, связанное с включением опции COLLATE в ее
объявление, например

DECLARE
    local_a text COLLATE "en_US";

Этот параметр переопределяет параметры сортировки, которые в противном
случае были бы переданы переменной в соответствии с приведенными выше
правилами.

Также, конечно, явные предложения COLLATE могут быть записаны внутри
функции, если требуется принудительно использовать конкретное
сопоставление в конкретной операции. Например,

CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "C";
END;
$$ LANGUAGE plpgsql;

Это переопределяет параметры сортировки, связанные со столбцами таблицы,
параметрами или локальными переменными, используемыми в выражении, так
же, как это происходит в простой команде SQL.

Выражения

Все выражения, используемые в инструкциях PL/pgSQL, обрабатываются с
использованием основного SQL- исполнителя сервера. Например, когда вы
пишете оператор PL/pgSQL, как

IF expression THEN ...

PL/pgSQL оценит выражение, подав запрос

SELECT expression

на основной движок SQL. При формировании команды SELECT любые вхождения
имен переменных PL/pgSQL заменяются параметрами, как подробно описано
в разделе Подстановка переменных. Это позволяет составить план запроса для SELECT
только один раз, а затем повторно использовать для последующих оценок с
различными значениями переменных. Таким образом, то, что действительно
происходит при первом использовании выражения, по сути, является
командой PREPARE. Например, если мы объявили две целочисленные
переменные x и y, и мы напишем

IF x < y THEN ...

то, что происходит за кулисами, эквивалентно

PREPARE statement_name(integer, integer) AS SELECT $1 < $2;

и затем этот подготовленный оператор будет EXECUTE d для каждого
выполнения оператора IF, с текущими значениями переменных PL/pgSQL,
предоставленными в качестве значений параметров. Обычно эти детали не
важны для пользователя PL/pgSQL, но их полезно знать при попытке
диагностировать проблему. Более подробная информация представлена в
разделе Планирование кэширования.

Основные положения

В этом и следующих разделах мы опишем все типы операторов, которые явно
понятны PL/pgSQL. Все, что не распознается как один из этих типов
операторов, считается командой SQL и отправляется на выполнение главному
ядру базы данных, как описано в разделе Выполнение команды без результата
и разделе Выполнение запроса с результатом в одну строку.

Присваивание

Присвоение значения переменной PL/pgSQL записывается так:

variable { := | = } expression;

Как объяснено ранее, выражение в таком выражении оценивается с помощью
команды SQL SELECT отправляемой основному ядру базы данных. Выражение
должно давать одно значение (возможно, значение строки, если переменная
является строкой или переменной записи). Целевая переменная может быть
простой переменной (необязательно квалифицируемой именем блока), полем
строки или переменной записи или элементом массива, который является
простой переменной или полем. Равное ( = ) может использоваться вместо
PL/SQL-совместимого :=.

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

Примеры:

tax := subtotal * 0.06;
my_record.user_id := 20;

Выполнение команды без результата

Для любой команды SQL, которая не возвращает строки, например, INSERT
без предложения RETURNING, вы можете выполнить команду в функции PL /
pgSQL, просто написав команду.

Любое имя переменной PL/pgSQL, отображаемое в тексте команды,
рассматривается как параметр, а затем текущее значение переменной
предоставляется в качестве значения параметра во время выполнения. Это
точно так же, как обработка, описанная ранее для выражений; подробности
см. в разделе Подстановка переменных.

При выполнении команды SQL таким способом PL/pgSQL может кэшировать и
повторно использовать план выполнения для команды, как обсуждалось в
разделе Планирование кэширования.

Иногда полезно оценить выражение или запрос SELECT но отбросить
результат, например, при вызове функции, которая имеет побочные эффекты,
но не имеет полезного значения результата. Чтобы сделать это в PL /
pgSQL, используйте оператор PERFORM:

PERFORM query;

Это выполняет query и отбрасывает результат. Напишите query, как и
команду SQL SELECT, но замените исходное ключевое слово SELECT на
PERFORM. Для запросов WITH используйте PERFORM а затем поместите запрос
в скобки. (В этом случае запрос может вернуть только одну строку).
Переменные PL/pgSQL будут подставлены в запрос так же, как для команд,
которые не возвращают результата, и план кэшируется таким же образом.
Кроме того, специальная переменная FOUND устанавливается в значение
true, если запрос выдал хотя бы одну строку, или в значение false, если
не было строк (см. раздел Получение статуса результата).

Заметка
Можно было бы ожидать, что написание SELECT напрямую приведет к этому
результату, но в настоящее время единственный приемлемый способ сделать
это — PERFORM. Команда SQL, которая может возвращать строки, например,
SELECT, будет отклонена как ошибка, если в ней нет предложения INTO,
как описано в следующем разделе.

Пример:

PERFORM create_mv('cs_session_page_requests_mv', my_query);

Выполнение запроса с результатом в одну строку

Результат команды SQL, дающей одну строку (возможно, из нескольких
столбцов), может быть присвоен переменной записи, переменной типа строки
или списку скалярных переменных. Это делается путем написания базовой
команды SQL и добавления предложения INTO. Например,

SELECT select_expressions INTO [STRICT] target FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] target;
UPDATE ... RETURNING expressions INTO [STRICT] target;
DELETE ... RETURNING expressions INTO [STRICT] target;

где target может быть переменной записи, переменной строки или
разделенным запятыми списком простых переменных и полей записи / строки.
Переменные PL/pgSQL будут подставлены в остальную часть запроса, и
план будет кэширован, как описано выше для команд, которые не возвращают
строки. Это работает для SELECT, INSERT / UPDATE / DELETE с RETURNING и
служебных команд, которые возвращают результаты набора строк (например,
EXPLAIN). За исключением предложения INTO, команда SQL такая же, как
она написана вне PL/pgSQL.

Заметка
Обратите внимание, что эта интерпретация SELECT с INTO сильно отличается
от обычной команды SELECT INTO QHB, в которой целью INTO
является вновь созданная таблица. Если вы хотите создать таблицу из
результата SELECT внутри функции PL/pgSQL, используйте синтаксис
CREATE TABLE… AS SELECT.

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

Предложение INTO может появляться практически в любом месте команды SQL.
Обычно он записывается либо непосредственно перед, либо сразу после
списка select_expressions в команде SELECT, либо в конце команды для
других типов команд. Рекомендуется следовать этому соглашению на случай,
если парсер PL/pgSQL станет более строгим в будущих версиях.

Если STRICT не указан в предложении INTO, тогда target будет установлен
на первую строку, возвращаемую запросом, или на нули, если запрос не
вернул ни одной строки. (Обратите внимание, что « первая строка » не
является четко определенной, если вы не использовали ORDER BY). Любые
строки результатов после первой строки отбрасываются. Вы можете
проверить специальную переменную FOUND (см. раздел Получение статуса результата), чтобы
определить, была ли возвращена строка:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employee % not found', myname;
END IF;

Если указан параметр STRICT, запрос должен возвращать ровно одну
строку, иначе будет сообщено об ошибке во время выполнения,
NO_DATA_FOUND (без строк) или TOO_MANY_ROWS (более одной строки). Вы
можете использовать блок исключения, если хотите перехватить ошибку,
например:

BEGIN
    SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE EXCEPTION 'employee % not found', myname;
        WHEN TOO_MANY_ROWS THEN
            RAISE EXCEPTION 'employee % not unique', myname;
END;

Успешное выполнение команды с помощью STRICT всегда устанавливает FOUND
в значение true.

Для INSERT / UPDATE / DELETE с RETURNING PL/pgSQL сообщает об ошибке
для более чем одной возвращенной строки, даже если STRICT не указан. Это
связано с тем, что нет такой опции, как ORDER BY с помощью которой можно
определить, какая затронутая строка должна быть возвращена.

Если для функции включен print_strict_params, то при возникновении
ошибки из-за несоблюдения требований STRICT часть DETAIL в сообщении об ошибке
будет содержать информацию о параметрах, переданных в запрос. Вы можете
изменить параметр print_strict_params для всех функций, установив
plpgsql.print_strict_params, хотя это plpgsql.print_strict_params
только на последующие компиляции функций. Вы также можете включить его
для каждой функции, используя опцию компилятора, например:

CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE
userid int;
BEGIN
    SELECT users.userid INTO STRICT userid
        FROM users WHERE users.username = get_userid.username;
    RETURN userid;
END
$$ LANGUAGE plpgsql;

При сбое эта функция может выдавать сообщение об ошибке, например

ERROR:  query returned no rows
DETAIL:  parameters: $1 = 'nosuchuser'
CONTEXT:  PL/pgSQL function get_userid(text) line 6 at SQL statement

Заметка
Опция STRICT соответствует поведению SELECT INTO и связанных операторов
Oracle PL/SQL.

Для обработки случаев, когда вам нужно обработать несколько строк
результатов из запроса SQL, см. раздел Цикл по результатам запроса.

Выполнение динамических команд

Часто вы захотите генерировать динамические команды внутри ваших функций
PL/pgSQL, то есть команды, которые будут включать разные таблицы или
разные типы данных при каждом их выполнении. Обычные попытки PL/pgSQL
кэшировать планы для команд (как описано в разделе Планирование кэширования) не будут
работать в таких сценариях. Для решения такого рода проблем предусмотрен
оператор EXECUTE :

EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];

где command-string — это выражение, дающее строку (типа text ),
содержащую команду, которая должна быть выполнена. Необязательной target
является переменная записи, переменная строки или разделенный запятыми
список простых переменных и полей записи / строки, в которые будут
сохраняться результаты команды. Необязательные выражения USING
предоставляют значения для вставки в команду.

Подстановка переменных PL/pgSQL в вычисляемой командной строке не
производится. Любые обязательные значения переменных должны быть
вставлены в командную строку по мере ее создания; или вы можете
использовать параметры, как описано ниже.

Также нет кэширования плана для команд, выполняемых через EXECUTE.
Вместо этого команда всегда планируется каждый раз, когда выполняется
оператор. Таким образом, командная строка может быть динамически создана
внутри функции для выполнения действий над различными таблицами и
столбцами.

Предложение INTO указывает, где должны быть назначены результаты команды
SQL, возвращающей строки. Если указан список строк или переменных, он
должен точно соответствовать структуре результатов запроса (когда
используется переменная записи, он автоматически настраивается на
соответствие структуре результатов). Если возвращается несколько строк,
только первая будет назначена переменной INTO. Если строки не
возвращаются, NULL присваивается переменной (или переменным) INTO. Если предложение
INTO не указано, результаты запроса отбрасываются.

Если задана опция STRICT выдается сообщение об ошибке, если запрос не
создает ровно одну строку.

В командной строке могут использоваться значения параметров, на которые
в команде ссылаются как $1, $2 и т. Д. Эти символы относятся к
значениям, указанным в предложении USING. Этот метод часто
предпочтительнее вставки значений данных в командную строку в виде
текста: он позволяет избежать накладных расходов во время выполнения
преобразования значений в текст и обратно, и он гораздо менее подвержен
атакам с использованием SQL-инъекций, поскольку нет необходимости
заключать в кавычки или побега. Примером является:

EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

Обратите внимание, что символы параметров могут использоваться только
для значений данных — если вы хотите использовать динамически
определяемые имена таблиц или столбцов, вы должны вставить их в
командную строку в текстовом виде. Например, если предыдущий запрос
необходимо выполнить для динамически выбранной таблицы, вы можете
сделать это:

EXECUTE 'SELECT count(*) FROM '
    || quote_ident(tabname)
    || ' WHERE inserted_by = $1 AND inserted <= $2'
   INTO c
   USING checked_user, checked_date;

Более чистый подход заключается в использовании %I спецификации format()
для имен таблиц или столбцов (строки, разделенные новой строкой,
объединяются):

EXECUTE format('SELECT count(*) FROM %I '
   'WHERE inserted_by = $1 AND inserted <= $2', tabname)
   INTO c
   USING checked_user, checked_date;

Другое ограничение на символы параметров заключается в том, что они
работают только в командах SELECT, INSERT, UPDATE и DELETE. В других
типах операторов (обычно называемых служебными операторами) вы должны
вставлять значения текстуально, даже если они являются просто значениями
данных.

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

SELECT INTO в настоящее время не поддерживается в EXECUTE; вместо этого
выполните простую команду SELECT и укажите INTO как часть самого EXECUTE.

Заметка
Оператор PL/pgSQL EXECUTE не связан с оператором EXECUTE SQL,
поддерживаемым сервером QHB. Оператор EXECUTE сервера нельзя
использовать непосредственно в функциях PL/pgSQL (и не нужен).

Пример 1. Заключение в кавычки значений в динамических запросах

При работе с динамическими командами вам часто приходится иметь дело с
экранированием апострофов. Рекомендованный метод заключения в кавычки
фиксированного текста в теле функции — это экранирование знаками доллара. (Если
у вас есть устаревший код, который не использует долларовые кавычки,
обратитесь к обзору в разделе Обработка кавычек, который может сэкономить
некоторые усилия при переводе указанного кода в более разумную схему.)

Динамические значения требуют тщательной обработки, поскольку они могут
содержать символы кавычек. Пример использования format()
(предполагается, что вы заключили в кавычки тело функции, поэтому
кавычки не нужно удваивать):

EXECUTE format('UPDATE tbl SET %I = $1 '
   'WHERE key = $2', colname) USING newvalue, keyvalue;

Также можно напрямую вызывать функции цитирования:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_literal(newvalue)
        || ' WHERE key = '
        || quote_literal(keyvalue);

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

Поскольку quote_literal помечен как STRICT, он всегда будет возвращать
ноль при вызове с нулевым аргументом. В приведенном выше примере, если
newvalue или keyvalue были нулевыми, вся строка динамического запроса
станет нулевой, что приведет к ошибке EXECUTE. Вы можете избежать этой
проблемы, используя функцию quote_nullable, которая работает так же,
как и quote_literal за исключением того, что при вызове с нулевым
аргументом она возвращает строку NULL. Например,

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_nullable(newvalue)
        || ' WHERE key = '
        || quote_nullable(keyvalue);

Если вы имеете дело со значениями, которые могут быть нулевыми, вы
должны обычно использовать quote_nullable вместо quote_literal.

Как всегда, нужно позаботиться о том, чтобы нулевые значения в запросе
не давали непредвиденных результатов. Например, WHERE

'WHERE key = ' || quote_nullable(keyvalue)

никогда не будет успешным, если значение keyvalue равно нулю, потому что
результат использования оператора равенства = с нулевым операндом всегда
равен нулю. Если вы хотите, чтобы значение null работало как обычное
значение ключа, вам необходимо переписать приведенное выше как

'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)

(В настоящее время IS NOT DISTINCT FROM обрабатывается гораздо менее
эффективно, чем =, поэтому не делайте этого, если не нужно. См. раздел Функции и операторы сравнения
для получения дополнительной информации о пустых значениях и IS
DISTINCT
).

Обратите внимание, что цитирование в знаках доллара полезно только для
цитирования фиксированного текста. Было бы очень плохой идеей написать
этот пример так:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = $$'
        || newvalue
        || '$$ WHERE key = '
        || quote_literal(keyvalue);

потому что он сломался бы, если бы содержимое newvalue содержало $$. То
же самое возражение будет относиться к любому другому ограничителю в знаках
доллара, который вы можете выбрать. Таким образом, для безопасного
цитирования текста, который не известен заранее, вы должны использовать
quote_literal, quote_nullable или quote_ident, в зависимости от
ситуации.

Динамические операторы SQL также можно безопасно создавать с помощью
функции format (см. раздел FORMAT). Например:

EXECUTE format('UPDATE tbl SET %I = %L '
   'WHERE key = %L', colname, newvalue, keyvalue);

%I эквивалентен quote_ident, а %L эквивалентен quote_nullable.
Функция format может использоваться вместе с предложением USING :

EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
   USING newvalue, keyvalue;

Эта форма лучше, потому что переменные обрабатываются в их собственном
формате типа данных, а не безоговорочно преобразуют их в текст и
заключают их в кавычки через %L Это также более эффективно.

Гораздо больший пример динамической команды и EXECUTE можно увидеть в
Примере 10, который создает и выполняет команду CREATE FUNCTION для
определения новой функции.

Получение статуса результата

Есть несколько способов определить эффект команды. Первый метод
заключается в использовании команды GET DIAGNOSTICS, которая имеет вид:

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

Эта команда позволяет получить индикаторы состояния системы. CURRENT
это шумовое слово (но см. Также GET STACKED DIAGNOSTICS в разделе Получение информации об ошибке).
Каждый item является ключевым словом, идентифицирующим
значение состояния, которое должно быть назначено указанной variable
(которое должно иметь правильный тип данных для его получения).
Доступные в настоящее время элементы состояния показаны в таблице 1.
Вместо токена SQL-standard = можно использовать двоеточие ( := ).
Пример:

GET DIAGNOSTICS integer_var = ROW_COUNT;

Таблица 1. Доступные элементы диагностики

Имя Тип Описание
ROW_COUNT bigint количество строк, обработанных самой последней командой SQL
PG_CONTEXT text строка (и) текста, описывающего текущий стек вызовов (см. раздел Получение информации о месте выполнения)

Второй метод определения эффектов команды — это проверка специальной
переменной с именем FOUND, которая имеет тип boolean. FOUND начинает
false в каждом вызове функции PL/pgSQL. Он устанавливается каждым из
следующих типов утверждений:

  • Оператор SELECT INTO устанавливает FOUND true, если назначена
    строка, и false, если строка не возвращается.

  • Оператор PERFORM устанавливает FOUND true, если он создает (и
    отбрасывает) одну или несколько строк, и false, если строка не
    создается.

  • Операторы UPDATE, INSERT и DELETE устанавливают FOUND true, если
    затронута хотя бы одна строка, и false, если строка не
    затрагивается.

  • FETCH устанавливает FOUND true, если возвращает строку, и false,
    если строка не возвращается.

  • Оператор MOVE устанавливает FOUND true, если он успешно перемещает
    курсор, в противном случае — false.

  • FOREACH FOR или FOREACH устанавливает FOUND true, если он
    повторяется один или несколько раз, в противном случае — false.
    FOUND устанавливается таким образом при выходе из цикла; внутри
    выполнения цикла FOUND не изменяется оператором цикла, хотя он может
    быть изменен выполнением других операторов в теле цикла.

  • Операторы RETURN QUERY и RETURN QUERY EXECUTE устанавливают FOUND
    true, если запрос возвращает хотя бы одну строку, и false, если
    строка не возвращается.

Другие операторы PL/pgSQL не изменяют состояние FOUND. В частности,
обратите внимание, что EXECUTE изменяет вывод GET DIAGNOSTICS, но не
меняет FOUND.

FOUND — локальная переменная в каждой функции PL/pgSQL ; любые
изменения в ней влияют только на текущую функцию.

Пустой оператор

Иногда полезен пустой оператор.
Например, когда одно плечо условия if / then /
else намеренно пусто. Для этого используйте оператор NULL :

NULL;

Например, следующие два фрагмента кода эквивалентны:

BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        NULL;  -- ignore the error
END;
BEGIN
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN  -- ignore the error
END;

Что предпочтительнее, это вопрос вкуса.

Заметка
В Oracle PL/SQL пустые списки операторов недопустимы, поэтому для
таких ситуаций требуются операторы NULL. PL/pgSQL позволяет просто
ничего не писать.

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

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

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

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

RETURN

RETURN expression;

RETURN с выражением завершает функцию и возвращает значение expression
вызывающей стороне. Эта форма используется для функций PL/pgSQL,
которые не возвращают набор.

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

Если вы объявили функцию с выходными параметрами, напишите просто RETURN
без выражения. Текущие значения переменных выходного параметра будут
возвращены.

Если вы объявили функцию, возвращающую void, оператор RETURN может быть
использован для досрочного выхода из функции; но не пишите выражение
после RETURN.

Возвращаемое значение функции нельзя оставлять неопределенным. Если
управление достигает конца блока верхнего уровня функции, не нажимая
оператор RETURN, произойдет ошибка времени выполнения. Однако это
ограничение не распространяется на функции с выходными параметрами и на
функции, возвращающие void. В этих случаях оператор RETURN
автоматически выполняется, если заканчивается блок верхнего уровня.

Несколько примеров:

-- functions returning a scalar type
RETURN 1 + 2;
RETURN scalar_var;

-- functions returning a composite type
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- must cast columns to correct types

RETURN NEXT и RETURN QUERY

RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];

Когда объявляется, что функция 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
        -- can do some processing here
        RETURN NEXT r; -- return current row of 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);

    -- Since execution is not finished, we can check whether rows were returned
    -- and raise exception if not.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END
$BODY$
LANGUAGE plpgsql;

-- Returns available flights or raises exception if there are no
-- available flights.
SELECT * FROM get_available_flightid(CURRENT_DATE);

Заметка
Текущая реализация RETURN NEXT и RETURN QUERY сохраняет весь набор
результатов перед возвратом из функции, как обсуждалось выше. Это
означает, что если функция PL/pgSQL выдает очень большой набор
результатов, производительность может быть низкой: данные будут записаны
на диск во избежание исчерпания памяти, но сама функция не вернется,
пока не будет сгенерирован весь набор результатов. Будущая версия
PL/pgSQL может позволить пользователям определять функции, возвращающие
множество, которые не имеют этого ограничения. В настоящее время точка,
с которой данные начинают записываться на диск, контролируется
переменной конфигурации work_mem. Администраторам, которые имеют
достаточно памяти для хранения больших наборов результатов в памяти,
следует рассмотреть возможность увеличения этого параметра.

Возврат из процедуры

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

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

Вызов процедуры

Функция, процедура или блок 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;  -- 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

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;

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;

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
    -- hmm, the only other possibility is that number is 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

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

Простая форма CASE обеспечивает условное выполнение, основанное на
равенстве операндов. search-expression оценивается (один раз) и
последовательно сравнивается с каждым expression в предложениях WHEN.
Если совпадение найдено, то соответствующие statements выполняются, а
затем управление переходит к следующему оператору после END CASE.
(Последующие выражения WHEN не оцениваются). Если совпадений не найдено,
выполняются операторы ELSE; но если 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 обеспечивает условное выполнение, основанное на
истинности логических выражений. boolean-expression каждого предложения
WHEN вычисляется по очереди, пока не будет найдено одно, которое выдает
true. Затем выполняются соответствующие statements, а затем управление
переходит к следующему оператору после END CASE. (Последующие выражения
WHEN не оцениваются). Если истинный результат не найден, выполняются
statements в блоке ELSE ; но если 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>> ]
LOOP
    statements
END LOOP [ label ];

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

EXIT

EXIT [ label ] [ WHEN boolean-expression ];

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

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

EXIT может использоваться со всеми типами циклов; он не ограничен
использованием с безусловными циклами.

При использовании с блоком BEGIN, EXIT передает управление следующему
оператору после конца блока. Обратите внимание, что для этой цели должна
использоваться метка; немаркированный EXIT никогда не считается
совпадающим с блоком BEGIN.

Примеры:

LOOP
    -- some computations
    IF count > 0 THEN
        EXIT;  -- exit loop
    END IF;
END LOOP;

LOOP
    -- some computations
    EXIT WHEN count > 0;  -- same result as previous example
END LOOP;

<<ablock>>
BEGIN
    -- some computations
    IF stocks > 100000 THEN
        EXIT ablock;  -- causes exit from the BEGIN block
    END IF;
    -- computations here will be skipped when stocks > 100000
END;

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;

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;

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

[ <<label>> ]
FOR name IN [ REVERSE ] expression .. expression [ BY expression ] 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), тело цикла вообще не выполняется. Ошибка не возникает.

Если к циклу FOR прикреплена label то на целочисленную переменную цикла
можно ссылаться с помощью квалифицированного имени, используя эту label.

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

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

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

target — это переменная записи, переменная строки или разделенный
запятыми список скалярных переменных. target назначается последовательно
каждой строке, полученной в результате query и тело цикла выполняется
для каждой строки.

Пример:

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

        -- Now "mviews" has one record with information about the materialized view

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

FOR-IN-EXECUTE — это еще один способ перебора строк:

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

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

Другой способ указать запрос, результаты которого должны быть повторены,
объявить его как курсор. Это описано в разделе Цикл по результату курсора.

Цикл по массивам

Цикл 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 обычно является только одна переменная,
она может быть списком переменных при циклическом просмотре массива
составных значений (записей). В этом случае для каждого элемента массива
переменные назначаются из последовательных столбцов составного значения.

При положительном значении 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
    declarations ]
BEGIN
    statements
EXCEPTION
    WHEN condition [ OR condition ... ] THEN
        handler_statements
    [ WHEN condition [ OR condition ... ] THEN
          handler_statements
      ... ]
END;

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

Имена condition могут быть любыми из указанных в Коды ошибок QHB. Название
категории соответствует любой ошибке в ее категории. Имя специального
условия OTHERS соответствует каждому типу ошибки, кроме QUERY_CANCELED
и ASSERT_FAILURE. (Можно, но часто неразумно, отлавливать эти два типа
ошибок по имени). Имена условий не чувствительны к регистру. Кроме того,
условие ошибки может быть указано кодом 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 'caught division_by_zero';
        RETURN x;
END;

Когда элемент управления достигает назначения для y, он потерпит
неудачу с ошибкой Division_by_zero. Это будет поймано предложением
EXCEPTION. Значение, возвращаемое в операторе RETURN будет увеличенным
значением x, но влияние команды UPDATE будет отменено. Однако команда
INSERT предшествующая блоку, не откатывается, поэтому в результате база
данных содержит Tom Jones, а не Joe Jones.

Заметка
Блок, содержащий предложение EXCEPTION, значительно дороже для входа и
выхода, чем для блока без такового. Поэтому не используйте EXCEPTION без
необходимости.

Пример 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
        -- first try to update the key
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Do nothing, and loop to try the UPDATE again.
        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 содержит код
ошибки, соответствующий SQLSTATE исключению . Специальная переменная SQLERRM содержит
сообщение об ошибке, связанной с исключением. Эти переменные не
определены вне обработчиков исключений.

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

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

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

Таблица 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 строка (и) текста, описывающего стек вызовов во время исключения (см. раздел Получение информации о месте выполнения)

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

Вот пример:

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- some processing which might cause an exception
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

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

Команда GET DIAGNOSTICS, ранее описанная в разделе Получение статуса результата, извлекает
информацию о текущем состоянии выполнения (в то время как команда 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 возвращает тот же вид
трассировки стека, но описывает местоположение, в котором была
обнаружена ошибка, а не текущее местоположение.

Курсоры

Вместо одновременного выполнения всего запроса можно установить курсор,
который инкапсулирует запрос, и затем прочитать результат запроса по
несколько строк за раз. Одна из причин этого состоит в том, чтобы
избежать переполнения памяти, когда результат содержит большое
количество строк. (Однако пользователям PL/pgSQL обычно не нужно
беспокоиться об этом, поскольку циклы FOR автоматически используют
курсор внутри, чтобы избежать проблем с памятью). Более интересным
является возвращение ссылки на курсор, созданный функцией, что позволяет
звонящий, чтобы прочитать строки. Это обеспечивает эффективный способ
возврата больших наборов строк из функций.

Объявление переменных курсора

Весь доступ к курсорам в PL/pgSQL осуществляется через переменные
курсора, которые всегда имеют специальный тип данных refcursor. Один из
способов создать курсорную переменную — просто объявить ее как
переменную типа refcursor. Другой способ — использовать синтаксис
объявления курсора, который в общем случае:

name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;

(FOR может быть заменен на IS для совместимости с Oracle). Если указан
SCROLL, курсор будет способен прокручиваться назад; если не указан NO
SCROLL, обратные выборки будут отклонены; если ни одна из спецификаций
не появляется, это зависит от запроса, будут ли разрешены обратные
выборки. arguments, если указан, представляет собой разделенный
запятыми список типов name datatype пар, которые определяют имена,
которые должны быть заменены значениями параметров в данном запросе.
Фактические значения для замены этих имен будут указаны позже, когда
курсор будет открыт.

Несколько примеров:

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;

Все три из этих переменных имеют тип данных refcursor, но первая
может использоваться с любым запросом, в то время как вторая имеет
полностью определенный запрос, уже связанный с ним, а последняя имеет
параметризованный запрос, связанный с ним. ( key будет заменен
целочисленным значением параметра при открытии курсора). Переменная
curs1 называется несвязанной, поскольку она не связана ни с каким
конкретным запросом.

Открытие курсоров

Прежде чем курсор можно использовать для извлечения строк, его
необходимо открыть. (Это действие эквивалентно команде SQL DECLARE
CURSOR
). PL/pgSQL имеет три формы оператора OPEN, две из которых
используют несвязанные переменные курсора, а третья использует связанную
переменную курсора.

Заметка
Переменные связанного курсора также можно использовать без явного
открытия курсора с помощью оператора FOR описанного в разделе Цикл по результату курсора.

OPEN FOR query

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

Переменная курсора открывается и получает заданный запрос для
выполнения. Курсор уже нельзя открыть, и он должен быть объявлен как
несвязанная переменная курсора (то есть как простая переменная refcursor
). Запрос должен быть SELECT или чем-то еще, что возвращает строки
(например, EXPLAIN ). Запрос обрабатывается так же, как и другие команды
SQL в PL/pgSQL : имена переменных PL/pgSQL заменяются, и план
запроса кэшируется для возможного повторного использования. Когда
переменная PL/pgSQL подставляется в запрос курсора, подставляется
значение, которое она имеет во время OPEN ; Последующие изменения в
переменной не влияют на поведение курсора. Параметры SCROLL и NO SCROLL
имеют те же значения, что и для привязанного курсора.

Пример:

OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;

OPEN FOR EXECUTE

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR EXECUTE query_string
                                     [ USING expression [, ... ] ];

Переменная курсора открывается и получает заданный запрос для
выполнения. Курсор уже нельзя открыть, и он должен быть объявлен как
несвязанная переменная курсора (то есть как простая переменная refcursor
). Запрос указывается как строковое выражение, так же, как в команде
EXECUTE. Как обычно, это обеспечивает гибкость, поэтому план запроса
может варьироваться от одного прогона к следующему (см. раздел Планирование кэширования
), а также означает, что в командной строке не производится подстановка
переменных. Как и в случае EXECUTE, значения параметров могут быть
вставлены в динамическую команду через format() и USING. Параметры
SCROLL и NO SCROLL имеют те же значения, что и для привязанного курсора.

Пример:

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

В этом примере имя таблицы вставляется в запрос через format().
Значение сравнения для col1 вставляется через параметр USING, поэтому
не нужно col1.

Открытие связанного курсора

OPEN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ];

Эта форма OPEN используется для открытия переменной курсора, запрос
которой был связан с ней, когда она была объявлена. Курсор уже не может
быть открыт. Список фактических выражений значений аргументов должен
появляться тогда и только тогда, когда указывалось, что курсор принимает
аргументы. Эти значения будут подставлены в запрос.

План запроса для привязанного курсора всегда считается кэшируемым; в
этом случае нет эквивалента EXECUTE. Обратите внимание, что SCROLL и NO
SCROLL
не могут быть указаны в OPEN, поскольку поведение прокрутки
курсора уже было определено.

Значения аргумента могут быть переданы с использованием позиционной или
именованной нотации. В позиционной нотации все аргументы указываются по
порядку. В именованной нотации имя каждого аргумента указывается с
помощью := чтобы отделить его от выражения аргумента. Как и в случае
вызова функций, описанных в разделе Вызов функции, также допускается смешивание
позиционной и именованной нотации.

Примеры (они используют примеры объявления курсора выше):

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);

Поскольку подстановка переменных выполняется в запросе связанного
курсора, на самом деле существует два способа передачи значений в
курсор: либо с явным аргументом OPEN, либо неявным образом путем ссылки
на переменную PL/pgSQL в запросе. Однако в него будут подставлены
только переменные, объявленные до объявления ограниченного курсора. В
любом случае передаваемое значение определяется во время OPEN.
Например, другой способ получить тот же эффект, что и в приведенном выше
примере с curs3 :

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;

Использование курсоров

Когда курсор открыт, им можно манипулировать с помощью описанных здесь
операторов.

Эти манипуляции не обязательно должны выполняться в той же функции,
которая открывала курсор для начала. Вы можете вернуть значение
refcursor из функции и позволить вызывающей стороне воздействовать на
курсор. (Внутренне значение refcursor — это просто строковое имя так
называемого портала, содержащего активный запрос для курсора. Это имя
можно передавать, присваивать другим переменным refcursor и т. д., Не
нарушая портал).

Все порталы неявно закрываются в конце транзакции. Поэтому значение
refcursor можно использовать для ссылки на открытый курсор только до
конца транзакции.

FETCH

FETCH [ direction { FROM | IN } ] cursor INTO target;

FETCH извлекает следующую строку из курсора в цель, которая может быть
переменной строки, переменной записи или списком простых переменных,
разделенных запятыми, подобно SELECT INTO. Если следующей строки нет,
для цели устанавливается значение NULL. Как и в случае SELECT INTO,
специальная переменная FOUND может быть проверена, чтобы увидеть, была
ли получена строка или нет.

Предложение direction может быть любым из вариантов, разрешенных в
команде SQL FETCH, кроме тех, которые могут извлекать более одной
строки; а именно, это может быть NEXT, PRIOR, FIRST, LAST, ABSOLUTE
count
, RELATIVE count, FORWARD или BACKWARD. direction пропуска
совпадает с указанием NEXT. В формах, использующих count, count может
быть любым целочисленным выражением (в отличие от команды SQL FETCH,
которая допускает только целочисленную константу). Значения direction
которые требуют перемещения назад, могут потерпеть неудачу, если курсор
не был объявлен или открыт с помощью опции SCROLL.

cursor должен быть именем переменной refcursor которая ссылается на
портал открытого курсора.

Примеры:

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;

MOVE

MOVE [ direction { FROM | IN } ] cursor;

MOVE перемещает курсор без получения каких-либо данных. MOVE работает
точно так же, как команда FETCH, за исключением того, что он только
перемещает курсор и не возвращает перемещенную строку. Как и в случае
SELECT INTO, специальная переменная FOUND может быть проверена, чтобы
увидеть, была ли следующая строка для перемещения.

Примеры:

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;

UPDATE/DELETE WHERE CURRENT OF

UPDATE table SET ... WHERE CURRENT OF cursor;
DELETE FROM table WHERE CURRENT OF cursor;

Когда курсор расположен в строке таблицы, эту строку можно обновить или
удалить, используя курсор для идентификации строки. Существуют
ограничения на то, каким может быть запрос курсора (в частности, нет
группировки), и лучше всего использовать FOR UPDATE в курсоре. Для
получения дополнительной информации см. Справочную страницу DECLARE.

Пример:

UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;

CLOSE

CLOSE cursor;

CLOSE закрывает портал под открытым курсором. Это может быть
использовано для освобождения ресурсов раньше, чем конец транзакции, или
для освобождения переменной курсора, которая будет открыта снова.

Пример:

CLOSE curs1;

Возврат курсора

Функции PL/pgSQL могут возвращать курсоры вызывающей стороне. Это
полезно для возврата нескольких строк или столбцов, особенно с очень
большими результирующими наборами. Для этого функция открывает курсор и
возвращает имя курсора вызывающей стороне (или просто открывает курсор,
используя имя портала, указанное или иным образом известное вызывающей
стороне). Затем вызывающая сторона может извлечь строки из курсора.
Курсор может быть закрыт вызывающим абонентом, или он будет закрыт
автоматически при закрытии транзакции.

Имя портала, используемое для курсора, может быть указано программистом
или сгенерировано автоматически. Чтобы указать имя портала, просто
присвойте строку переменной refcursor перед ее открытием. Строковое
значение переменной refcursor будет использоваться OPEN в качестве имени
базового портала. Однако, если переменная refcursor имеет значение null,
OPEN автоматически генерирует имя, которое не конфликтует с каким-либо
существующим порталом, и присваивает его переменной refcursor.

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

В следующем примере показан один из способов, которым вызывающее лицо
может предоставить имя курсора:

CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS '
BEGIN
    OPEN $1 FOR SELECT col FROM test;
    RETURN $1;
END;
' LANGUAGE plpgsql;

BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;

В следующем примере используется автоматическая генерация имени курсора:

CREATE FUNCTION reffunc2() RETURNS refcursor AS '
DECLARE
    ref refcursor;
BEGIN
    OPEN ref FOR SELECT col FROM test;
    RETURN ref;
END;
' LANGUAGE plpgsql;

-- need to be in a transaction to use cursors.
BEGIN;
SELECT reffunc2();

      reffunc2
--------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;

В следующем примере показан один способ вернуть несколько курсоров из
одной функции:

CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT * FROM table_1;
    RETURN NEXT $1;
    OPEN $2 FOR SELECT * FROM table_2;
    RETURN NEXT $2;
END;
$$ LANGUAGE plpgsql;

-- need to be in a transaction to use cursors.
BEGIN;

SELECT * FROM myfunc('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;

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

Существует вариант оператора FOR который позволяет перебирать строки,
возвращаемые курсором. Синтаксис:

[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ] LOOP
    statements
END LOOP [ label ];

Переменная курсора должна быть привязана к какому-либо запросу, когда
она была объявлена, и ее уже нельзя открыть. Оператор FOR автоматически
открывает курсор и снова закрывает курсор при выходе из цикла. Список
фактических выражений значений аргументов должен появляться тогда и
только тогда, когда указывалось, что курсор принимает аргументы. Эти
значения будут подставлены в запросе точно так же, как во время OPEN
(см. раздел Открытие связанного курсора).

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

Управление транзакциями

В процедурах, вызываемых командой CALL, а также в блоках анонимного кода
(команда DO ), можно завершать транзакции с помощью команд COMMIT и
ROLLBACK. Новая транзакция запускается автоматически после завершения
транзакции с использованием этих команд, поэтому отдельной команды START
TRANSACTION
нет. (Обратите внимание, что BEGIN и END имеют разные значения
в PL/pgSQL).

Простой пример:

CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
    FOR i IN 0..9 LOOP
        INSERT INTO test1 (a) VALUES (i);
        IF i % 2 = 0 THEN
            COMMIT;
        ELSE
            ROLLBACK;
        END IF;
    END LOOP;
END
$$;

CALL transaction_test1();

Новая транзакция начинается с характеристик транзакции по умолчанию,
таких как уровень изоляции транзакции. В случаях, когда транзакции
фиксируются в цикле, может быть желательно автоматически начинать новые
транзакции с теми же характеристиками, что и предыдущая. Команды COMMIT
AND CHAIN
и ROLLBACK AND CHAIN выполняют это.

Управление транзакциями возможно только в CALL или DO из верхнего уровня
или во вложенных CALL или DO без какой-либо другой промежуточной
команды. Например, если стеком вызовов является
CALL proc1() → CALL proc2() → CALL proc3(), то вторая и третья процедуры могут выполнять
действия по управлению транзакциями. Но если стеком вызовов является
CALL proc1() → SELECT func2() → CALL proc3(), то последняя процедура не
может осуществлять управление транзакциями из-за промежуточного SELECT.

Особые соображения применимы к циклам курсора. Рассмотрим этот пример:

CREATE PROCEDURE transaction_test2()
LANGUAGE plpgsql
AS $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN SELECT * FROM test2 ORDER BY x LOOP
        INSERT INTO test1 (a) VALUES (r.x);
        COMMIT;
    END LOOP;
END;
$$;

CALL transaction_test2();

Обычно курсоры автоматически закрываются при фиксации транзакции. Однако
курсор, созданный как часть цикла, подобного этому, автоматически
преобразуется в удерживаемый курсор с помощью первого COMMIT или
ROLLBACK. Это означает, что курсор полностью вычисляется в первом
COMMIT или ROLLBACK а не по строкам. Курсор по-прежнему автоматически
удаляется после цикла, поэтому он в большинстве случаев невидим для
пользователя.

Команды транзакции не допускаются в циклах курсора, управляемых
командами, которые не предназначены только для чтения (например, UPDATE
… RETURNING
).

Транзакция не может быть завершена внутри блока с обработчиками
исключений.

Ошибки и сообщения

Вывод ошибок и сообщений

Используйте оператор RAISE чтобы выводить расширенные сообщения и
обрабатывать ошибки.

RAISE [ level ] 'format' [, expression [, ... ]] [ USING option = expression [, ... ] ];
RAISE [ level ] condition_name [ USING option = expression [, ... ] ];
RAISE [ level ] SQLSTATE 'sqlstate' [ USING option = expression [, ... ] ];
RAISE [ level ] USING option = expression [, ... ];
RAISE ;

Параметр level указывает серьезность ошибки. Допустимые уровни: DEBUG,
LOG, INFO, NOTICE, WARNING и EXCEPTION, по умолчанию используется
EXCEPTION. EXCEPTION вызывает ошибку (которая обычно прерывает текущую
транзакцию); другие уровни генерируют только сообщения с различными
уровнями приоритета. Независимо от того, передаются ли сообщения
определенного приоритета клиенту, записываются ли они в журнал сервера,
или и то, и другое контролируются переменными конфигурации
log_min_messages и client_min_messages. См. главу Конфигурация сервера для получения
дополнительной информации.

После level если он есть, вы можете написать format (который должен быть
простым строковым литералом, а не выражением). Строка формата определяет
текст сообщения об ошибке, о которой будет сообщено. За форматной
строкой могут следовать необязательные выражения аргумента, которые
будут вставлены в сообщение. Внутри строки формата % заменяется
строковым представлением значения следующего необязательного аргумента.
Напишите %% чтобы получить буквальный %. Количество аргументов должно
соответствовать количеству % заполнителей в строке формата, иначе при
компиляции функции возникает ошибка.

В этом примере значение v_job_id заменит % в строке:

RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;

Вы можете прикрепить дополнительную информацию к отчету об ошибке,
написав USING а затем option = expression items. Каждое expression может
быть любым строковым выражением. Допустимые ключевые слова option:

Option
MESSAGE Устанавливает текст сообщения об ошибке. Эта опция не может использоваться в форме RAISE которая включает строку формата перед USING.
DETAIL Предоставляет подробное сообщение об ошибке.
HINT Предоставляет подсказку.
ERRCODE Указывает код ошибки (SQLSTATE) для отчета либо по имени условия, как показано в Коды ошибок QHB, либо непосредственно в виде пятизначного кода SQLSTATE.
COLUMN
CONSTRAINT
DATATYPE
TABLE SCHEMA
Предоставляют имя связанного объекта.

Этот пример прервет транзакцию с данным сообщением об ошибке и
подсказкой:

RAISE EXCEPTION 'Nonexistent ID --> %', user_id
      USING HINT = 'Please check your user ID';

Эти два примера показывают эквивалентные способы установки SQLSTATE:

RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';

Существует второй синтаксис RAISE в котором основным аргументом является
имя условия или SQLSTATE, о котором необходимо сообщить, например:

RAISE division_by_zero;
RAISE SQLSTATE '22012';

В этом синтаксисе USING может использоваться для предоставления
пользовательского сообщения об ошибке, детализации или подсказки. Другой
способ сделать предыдущий пример

RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;

Еще один вариант — написать RAISE USING или RAISE level USING и
поместить все остальное в список USING.

Последний вариант RAISE не имеет параметров вообще. Эта форма может
использоваться только внутри предложения EXCEPTION блока BEGIN; это
приводит к тому, что ошибка, обрабатываемая в данный момент, будет
переброшена.

Заметка
До PostgreSQL 9.1 RAISE без параметров интерпретировалось как повторное
генерирование ошибки из блока, содержащего активный обработчик
исключений. Таким образом, предложение EXCEPTION, вложенное в этот
обработчик, не может его перехватить, даже если RAISE находится в блоке
вложенного предложения EXCEPTION. Это было сочтено удивительным, а
также несовместимым с Oracle PL/SQL.

Если ни имя условия, ни SQLSTATE не указаны в команде RAISE EXCEPTION,
по умолчанию используется ERRCODE_RAISE_EXCEPTION ( P0001 ). Если
текст сообщения не указан, по умолчанию используется имя условия или
SQLSTATE в качестве текста сообщения.

Заметка
При указании кода ошибки с помощью кода SQLSTATE вы не ограничены
предварительно определенными кодами ошибок, но можете выбрать любой код
ошибки, состоящий из пяти цифр и / или букв ASCII в верхнем регистре,
кроме 00000. Рекомендуется избегать выдачи кодов ошибок, которые
заканчиваются тремя нулями, потому что это коды категорий, которые могут
быть захвачены только путем захвата всей категории.

Проверка утверждений

Оператор ASSERT является удобным сокращением для вставки проверок
отладки в функции PL/pgSQL.

ASSERT condition [, message ];

condition является логическим выражением, которое, как ожидается, всегда
будет иметь значение true; если это так, оператор ASSERT больше ничего
не делает. Если результатом является ложь или ASSERT_FAILURE, то
возникает исключение ASSERT_FAILURE. (Если ошибка возникает при
оценке condition, она сообщается как нормальная ошибка).

Если предоставляется необязательное message, это выражение, результат
которого (если не нулевой) заменяет текст сообщения об ошибке по
умолчанию «утверждение не выполнено», если condition не выполнено.
Выражение message не оценивается в обычном случае, когда утверждение
успешно выполняется.

Тестирование утверждений можно включить или выключить с помощью
параметра конфигурации plpgsql.check_asserts, который принимает
логическое значение; по умолчанию включено. Если этот параметр off то
операторы ASSERT ничего не делают.

Обратите внимание, что ASSERT предназначен для обнаружения программных
ошибок, а не для сообщения об обычных ошибках. Для этого используйте
оператор RAISE, описанный выше.

Триггерные функции

PL/pgSQL может использоваться для определения триггерных функций при
изменении данных или событий базы данных. Триггерная функция создается с
помощью команды CREATE FUNCTION, объявляющей ее как функцию без
аргументов и возвращаемого типа trigger (для триггеров изменения данных)
или event_trigger (для триггеров событий базы данных). Специальные
локальные переменные с именем *TG_

  • автоматически определяются
    для описания условия, которое вызвало вызов.

Триггеры при изменении данных

Триггер изменения данных объявляется как функция без аргументов и
возвращаемого типа trigger. Обратите внимание, что функция должна быть
объявлена без аргументов, даже если она ожидает получить некоторые
аргументы, указанные в CREATE TRIGGER — такие аргументы передаются через
TG_ARGV, как описано ниже.

Когда в качестве триггера вызывается функция PL/pgSQL, в блоке
верхнего уровня автоматически создаются несколько специальных
переменных. Вот они:

Переменная
NEW Тип данных RECORD; переменная, содержащая новую строку базы данных для операций INSERT/UPDATE в триггерах уровня строки. Эта переменная равна нулю в триггерах уровня оператора и для операций DELETE.
OLD Тип данных RECORD; переменная, содержащая старую строку базы данных для операций UPDATE / DELETE в триггерах уровня строки. Эта переменная равна нулю в триггерах уровня оператора и для операций INSERT.
NEW Тип данных RECORD; переменная, содержащая новую строку базы данных для операций INSERT/UPDATE в триггерах уровня строки. Эта переменная равна нулю в триггерах уровня оператора и для операций DELETE.
OLD Тип данных RECORD; переменная, содержащая старую строку базы данных для операций UPDATE / DELETE в триггерах уровня строки. Эта переменная равна нулю в триггерах уровня оператора и для операций INSERT.
TG_NAME Тип данных name; переменная, которая содержит имя фактически сработавшего триггера.
TG_WHEN Тип данных text; строка BEFORE, AFTER или INSTEAD OF, в зависимости от определения триггера.
TG_LEVEL Тип данных text; строка ROW или STATEMENT зависимости от определения триггера.
TG_OP Тип данных text; строка INSERT, UPDATE, DELETE или TRUNCATE указывающая, для какой операции сработал триггер.
TG_RELID Тип данных oid; идентификатор объекта таблицы, вызвавшей вызов триггера.
TG_RELNAME Тип данных name; имя таблицы, вызвавшей вызов триггера. Теперь это устарело и может исчезнуть в будущем выпуске. TG_TABLE_NAME этого используйте TG_TABLE_NAME.
TG_TABLE_NAME Тип данных name; имя таблицы, вызвавшей вызов триггера.
TG_TABLE_SCHEMA Тип данных name; имя схемы таблицы, вызвавшей вызов триггера./dd>
TG_NARGS Тип данных integer; количество аргументов, переданных функции триггера в операторе CREATE TRIGGER.
TG_ARGV[] Тип данных массив text; аргументы из оператора CREATE TRIGGER. Индекс отсчитывает от 0. Недопустимые индексы (меньше 0 или больше или равны tg_nargs) приводят к нулевому значению.

Функция триггера должна возвращать либо NULL либо значение записи/строки,
имеющее точно структуру таблицы, для которой был запущен триггер.

Триггеры уровня строки срабатывают до того, как они могут вернуть ноль, чтобы
сигнализировать диспетчеру триггеров пропустить оставшуюся часть
операции для этой строки (т.е. последующие триггеры не запускаются, и
INSERT/UPDATE/DELETE для этой строки не происходит). Если
возвращается ненулевое значение, то операция продолжается с этим
значением строки. Возвращение значения строки, отличного от исходного
значения NEW изменяет строку, которая будет вставлена или обновлена.
Таким образом, если триггерная функция хочет, чтобы инициирующее
действие успешно выполнялось без изменения значения строки, необходимо
вернуть NEW (или равное ему значение). Чтобы изменить строку, подлежащую
сохранению, можно заменить отдельные значения непосредственно в NEW и
вернуть измененный NEW, или создать полную новую запись / строку для
возврата. В случае до-триггера на DELETE, возвращаемое значение не
имеет прямого эффекта, но оно должно быть ненулевым, чтобы позволить
действию триггера продолжаться. Обратите внимание, что NEW является
нулем в триггерах DELETE, поэтому возвращать его обычно не имеет
смысла. Обычная идиома в триггерах DELETE — возвращать OLD.

INSTEAD OF (которые всегда являются триггерами уровня строки и могут
использоваться только в представлениях) могут возвращать ноль, чтобы
указать, что они не выполняли никаких обновлений и что оставшаяся часть
операции для этой строки должна быть пропущена (т.е. последующие
триггеры не срабатывают, и строка не учитывается в состоянии, затронутом
строками для окружающего INSERT/UPDATE/DELETE). В противном случае
должно быть возвращено ненулевое значение, чтобы сигнализировать, что
триггер выполнил запрошенную операцию. Для операций INSERT и UPDATE
возвращаемое значение должно быть NEW, которое триггерная функция может
изменить для поддержки INSERT RETURNING и UPDATE RETURNING (это также
повлияет на значение строки, передаваемое любым последующим триггерам
или передаваемое в специальную ссылку EXCLUDED alias в пределах Оператор
INSERT с предложением ON CONFLICT DO UPDATE). Для операций DELETE
возвращаемое значение должно быть OLD.

Возвращаемое значение срабатывания триггера уровня строки AFTER или
триггера уровня оператора BEFORE или AFTER всегда игнорируется; это
также может быть нулевым. Однако любой из этих типов триггеров может все
же прервать всю операцию, вызвав ошибку.

Пример 3 показывает вариант функции триггера в PL/pgSQL.

Пример 3. Функция запуска PL/pgSQL

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

CREATE TABLE emp (
    empname text,
    salary integer,
    last_date timestamp,
    last_user text
);

CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Check that empname and salary are given
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
        END IF;

        -- Who works for us when they must pay for it?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
        END IF;

        -- Remember who changed the payroll when
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
    FOR EACH ROW EXECUTE FUNCTION emp_stamp();

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

Пример 4. Функция запуска PL/pgSQL для аудита

Этот пример триггера гарантирует, что любая вставка, обновление или
удаление строки в таблице emp записано (т.е. проверено) в таблице
emp_audit. Текущее время и имя пользователя указываются в строке
вместе с типом выполняемой над ней операции.

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Create a row in emp_audit to reflect the operation performed on emp,
        -- making use of the special variable TG_OP to work out the operation.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
        END IF;
        RETURN NULL; -- result is ignored since this is an AFTER trigger
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
    FOR EACH ROW EXECUTE FUNCTION process_emp_audit();

Вариант предыдущего примера использует представление, соединяющее
основную таблицу с таблицей аудита, чтобы показать, когда каждая запись
последний раз изменялась. Этот подход по-прежнему регистрирует полный
контрольный журнал изменений таблицы, но также представляет упрощенное
представление контрольного журнала, показывая только последнюю
измененную временную метку, полученную из контрольного журнала для
каждой записи. Пример 5 показывает вариант триггера аудита для
представления в PL/pgSQL.

Пример 5. Функция запуска PL/pgSQL View для аудита

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

CREATE TABLE emp (
    empname           text PRIMARY KEY,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer,
    stamp             timestamp NOT NULL
);

CREATE VIEW emp_view AS
    SELECT e.empname,
           e.salary,
           max(ea.stamp) AS last_updated
      FROM emp e
      LEFT JOIN emp_audit ea ON ea.empname = e.empname
     GROUP BY 1, 2;

CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
    BEGIN
        --
        -- Perform the required operation on emp, and create a row in emp_audit
        -- to reflect the change made to emp.
        --
        IF (TG_OP = 'DELETE') THEN
            DELETE FROM emp WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            OLD.last_updated = now();
            INSERT INTO emp_audit VALUES('D', user, OLD.*);
            RETURN OLD;
        ELSIF (TG_OP = 'UPDATE') THEN
            UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('U', user, NEW.*);
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp VALUES(NEW.empname, NEW.salary);

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('I', user, NEW.*);
            RETURN NEW;
        END IF;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
    FOR EACH ROW EXECUTE FUNCTION update_emp_view();

Одним из применений триггеров является ведение сводной таблицы другой
таблицы. Результирующая сводка может использоваться вместо исходной
таблицы для определенных запросов — часто с значительно сокращенным
временем выполнения. Этот метод обычно используется в хранилищах данных,
где таблицы измеренных или наблюдаемых данных (так называемые таблицы
фактов) могут быть чрезвычайно большими. В примере 6 показан вариант
функции триггера в PL/pgSQL, которая поддерживает сводную таблицу для
таблицы фактов в хранилище данных.

Пример 6. Функция запуска PL/pgSQL для ведения сводной таблицы

Схема, подробно описанная здесь, частично основана на примере магазина
продуктов из набора инструментов хранилища данных Ральфа Кимбалла.

--
-- Main tables - time dimension and sales fact.
--
CREATE TABLE time_dimension (
    time_key                    integer NOT NULL,
    day_of_week                 integer NOT NULL,
    day_of_month                integer NOT NULL,
    month                       integer NOT NULL,
    quarter                     integer NOT NULL,
    year                        integer NOT NULL
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);

CREATE TABLE sales_fact (
    time_key                    integer NOT NULL,
    product_key                 integer NOT NULL,
    store_key                   integer NOT NULL,
    amount_sold                 numeric(12,2) NOT NULL,
    units_sold                  integer NOT NULL,
    amount_cost                 numeric(12,2) NOT NULL
);
CREATE INDEX sales_fact_time ON sales_fact(time_key);

--
-- Summary table - sales by time.
--
CREATE TABLE sales_summary_bytime (
    time_key                    integer NOT NULL,
    amount_sold                 numeric(15,2) NOT NULL,
    units_sold                  numeric(12) NOT NULL,
    amount_cost                 numeric(15,2) NOT NULL
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);

--
-- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
--
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
AS $maint_sales_summary_bytime$
    DECLARE
        delta_time_key          integer;
        delta_amount_sold       numeric(15,2);
        delta_units_sold        numeric(12);
        delta_amount_cost       numeric(15,2);
    BEGIN

        -- Work out the increment/decrement amount(s).
        IF (TG_OP = 'DELETE') THEN

            delta_time_key = OLD.time_key;
            delta_amount_sold = -1 * OLD.amount_sold;
            delta_units_sold = -1 * OLD.units_sold;
            delta_amount_cost = -1 * OLD.amount_cost;

        ELSIF (TG_OP = 'UPDATE') THEN

            -- forbid updates that change the time_key -
            -- (probably not too onerous, as DELETE + INSERT is how most
            -- changes will be made).
            IF ( OLD.time_key != NEW.time_key) THEN
                RAISE EXCEPTION 'Update of time_key : % -> % not allowed',
                                                      OLD.time_key, NEW.time_key;
            END IF;

            delta_time_key = OLD.time_key;
            delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
            delta_units_sold = NEW.units_sold - OLD.units_sold;
            delta_amount_cost = NEW.amount_cost - OLD.amount_cost;

        ELSIF (TG_OP = 'INSERT') THEN

            delta_time_key = NEW.time_key;
            delta_amount_sold = NEW.amount_sold;
            delta_units_sold = NEW.units_sold;
            delta_amount_cost = NEW.amount_cost;

        END IF;


        -- Insert or update the summary row with the new values.
        <<insert_update>>
        LOOP
            UPDATE sales_summary_bytime
                SET amount_sold = amount_sold + delta_amount_sold,
                    units_sold = units_sold + delta_units_sold,
                    amount_cost = amount_cost + delta_amount_cost
                WHERE time_key = delta_time_key;

            EXIT insert_update WHEN found;

            BEGIN
                INSERT INTO sales_summary_bytime (
                            time_key,
                            amount_sold,
                            units_sold,
                            amount_cost)
                    VALUES (
                            delta_time_key,
                            delta_amount_sold,
                            delta_units_sold,
                            delta_amount_cost
                           );

                EXIT insert_update;

            EXCEPTION
                WHEN UNIQUE_VIOLATION THEN
                    -- do nothing
            END;
        END LOOP insert_update;

        RETURN NULL;

    END;
$maint_sales_summary_bytime$ LANGUAGE plpgsql;

CREATE TRIGGER maint_sales_summary_bytime
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
    FOR EACH ROW EXECUTE FUNCTION maint_sales_summary_bytime();

INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
SELECT * FROM sales_summary_bytime;
DELETE FROM sales_fact WHERE product_key = 1;
SELECT * FROM sales_summary_bytime;
UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;

Триггеры AFTER также могут использовать таблицы переходов для проверки
всего набора строк, измененных оператором запуска. Команда CREATE
TRIGGER
назначает имена одной или обеим таблицам переходов, а затем
функция может ссылаться на эти имена, как если бы они были временными
таблицами только для чтения (см. Пример 7).

Пример 7. Аудит с таблицами переходов

Этот пример дает те же результаты, что и Пример 4, но вместо
использования триггера, который срабатывает для каждой строки, он
использует триггер, который срабатывает один раз для каждого оператора,
после сбора соответствующей информации в таблице переходов. Это может
быть значительно быстрее, чем подход с триггером строки, когда оператор
вызова изменил много строк. Обратите внимание, что мы должны сделать
отдельное объявление триггера для каждого типа события, так как
предложения REFERENCING должны быть разными для каждого случая. Но это
не мешает нам использовать одну функцию триггера, если мы выберем. (На
практике может быть лучше использовать три отдельные функции и избегать
тестов во время выполнения на TG_OP).

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Create rows in emp_audit to reflect the operations performed on emp,
        -- making use of the special variable TG_OP to work out the operation.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit
                SELECT 'D', now(), user, o.* FROM old_table o;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit
                SELECT 'U', now(), user, n.* FROM new_table n;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit
                SELECT 'I', now(), user, n.* FROM new_table n;
        END IF;
        RETURN NULL; -- result is ignored since this is an AFTER trigger
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit_ins
    AFTER INSERT ON emp
    REFERENCING NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_upd
    AFTER UPDATE ON emp
    REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_del
    AFTER DELETE ON emp
    REFERENCING OLD TABLE AS old_table
    FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();

Триггеры на события

PL/pgSQL может использоваться для определения триггеров событий.
QHB требует, чтобы функция, вызываемая как триггер события, была
объявлена как функция без аргументов и возвращаемого типа event_trigger.

Когда функция PL/pgSQL вызывается как триггер события, в блоке
верхнего уровня автоматически создаются несколько специальных
переменных. Они есть:

TG_EVENT

  • Тип данных text; строка, представляющая событие, для которого
    запускается триггер.

TG_TAG

  • Тип данных text; переменная, содержащая тег команды, для которого
    срабатывает триггер.

Пример 8 показывает пример функции триггера событий в PL/pgSQL.

Пример 8. Функция запуска событий PL/pgSQL

Этот пример триггера просто вызывает сообщение NOTICE каждый раз, когда
выполняется поддерживаемая команда.

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
    RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;

CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();

PL/pgSQL под капотом

В этом разделе обсуждаются некоторые детали реализации, которые часто
важно знать пользователям PL/pgSQL.

Подстановка переменных

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

INSERT INTO foo (foo) VALUES (foo);

Первое вхождение foo должно быть синтаксически именем таблицы, поэтому
оно не будет подставлено, даже если функция имеет переменную с именем
foo. Второе вхождение должно быть именем столбца таблицы, поэтому оно
также не будет подставлено. Только третье вхождение является кандидатом
на ссылку на переменную функции.

Поскольку имена переменных синтаксически ничем не отличаются от имен
столбцов таблицы, в выражениях, которые также относятся к таблицам,
может быть неоднозначность: подразумевается ли данное имя для ссылки на
столбец таблицы или переменную? Давайте изменим предыдущий пример на

INSERT INTO dest (col) SELECT foo + bar FROM src;

Здесь dest и src должны быть именами таблиц, а col должен быть столбцом
dest, но foo и bar могут быть либо переменными функции, либо столбцами
src.

По умолчанию PL/pgSQL сообщит об ошибке, если имя в выражении SQL
может ссылаться либо на переменную, либо на столбец таблицы. Вы можете
решить эту проблему, переименовав переменную или столбец, или
квалифицируя неоднозначную ссылку, или указав PL/pgSQL, какую
интерпретацию выбрать.

Самое простое решение — переименовать переменную или столбец. Общим
правилом кодирования является использование другого соглашения об именах
для переменных PL/pgSQL, чем для имен столбцов. Например, если вы
последовательно называете переменные функции v_something, в то
время как ни одно из имен столбцов не начинается с v_,
никаких конфликтов не будет.

В качестве альтернативы вы можете квалифицировать неоднозначные ссылки,
чтобы прояснить их. В приведенном выше примере src.foo будет однозначной
ссылкой на столбец таблицы. Чтобы создать однозначную ссылку на
переменную, объявите ее в помеченном блоке и используйте метку блока
(см. раздел Структура PL/pgSQL). Например,

<<block>>
DECLARE
    foo int;
BEGIN
    foo := ...;
    INSERT INTO dest (col) SELECT block.foo + bar FROM src;

Здесь block.foo означает переменную, даже если в src есть столбец foo.
Параметры функции, а также специальные переменные, такие как FOUND,
могут быть квалифицированы по имени функции, поскольку они неявно
объявляются во внешнем блоке, помеченном именем функции.

Иногда нецелесообразно исправлять все неоднозначные ссылки в большом
объеме кода PL/pgSQL. В таких случаях вы можете указать, что
PL/pgSQL должен разрешать неоднозначные ссылки как переменную или как столбец
таблицы (который совместим с некоторыми другими системами, такими как
Oracle),

Чтобы изменить это поведение в масштабе всей системы, установите для
параметра конфигурации plpgsql.variable_conflict один из параметров
error, use_variable или use_column (где error — заводская настройка
по умолчанию). Этот параметр влияет на последующие компиляции операторов
в функциях PL/pgSQL, но не на операторы, уже скомпилированные в
текущем сеансе. Поскольку изменение этого параметра может привести к
неожиданным изменениям в поведении функций PL/pgSQL, его может
изменить только суперпользователь.

Вы также можете установить поведение для каждой функции, вставив одну из
этих специальных команд в начале текста функции:

#variable_conflict error
#variable_conflict use_variable
#variable_conflict use_column

Эти команды влияют только на функцию, в которой они написаны, и
переопределяют настройку plpgsql.variable_conflict. Примером является

CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
    #variable_conflict use_variable
    DECLARE
        curtime timestamp := now();
    BEGIN
        UPDATE users SET last_modified = curtime, comment = comment
          WHERE users.id = id;
    END;
$$ LANGUAGE plpgsql;

В команде UPDATE curtime, comment и id будут ссылаться на переменную
функции и параметры независимо от того, есть ли у users столбцы с этими
именами. Обратите внимание, что нам нужно было квалифицировать ссылку на
users.id в WHERE, чтобы она ссылалась на столбец таблицы. Но нам не
нужно было указывать ссылку на comment как цель в списке UPDATE, потому
что синтаксически это должен быть столбец users. Мы можем написать одну
и ту же функцию, не завися от настройки variable_conflict следующим
образом:

CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
    <<fn>>
    DECLARE
        curtime timestamp := now();
    BEGIN
        UPDATE users SET last_modified = fn.curtime, comment = stamp_user.comment
          WHERE users.id = stamp_user.id;
    END;
$$ LANGUAGE plpgsql;

Подстановка переменных не происходит в командной строке, данной EXECUTE
или в одном из ее вариантов. Если вам нужно вставить переменное значение
в такую команду, сделайте это как часть построения строкового значения
или используйте USING, как показано в разделе Выполнение динамических команд.

Подстановка переменных в настоящее время работает только в командах
SELECT, INSERT, UPDATE и DELETE, поскольку основной механизм SQL
допускает параметры запроса только в этих командах. Чтобы использовать
непостоянное имя или значение в других типах операторов (обычно
называемых служебными операторами), необходимо создать служебный
оператор в виде строки и EXECUTE.

Планирование кэширования

Интерпретатор PL/pgSQL анализирует исходный текст функции и создает
внутреннее двоичное дерево инструкций при первом вызове функции (в
каждом сеансе). Дерево команд полностью переводит структуру операторов
PL/pgSQL, но отдельные выражения SQL и команды SQL, используемые в
функции, не переводятся немедленно.

Поскольку каждое выражение и команда SQL сначала выполняются в функции,
интерпретатор PL/pgSQL анализирует и анализирует команду, чтобы
создать подготовленный оператор, используя функцию SPI_prepare
менеджера SPI. Последующие посещения этого выражения или команды
повторно используют подготовленное утверждение. Таким образом, функция с
условными путями кода, которые редко посещаются, никогда не будет нести
затраты на анализ тех команд, которые никогда не выполняются в текущем
сеансе. Недостатком является то, что ошибки в определенном выражении или
команде не могут быть обнаружены, пока эта часть функции не будет
достигнута во время выполнения. (Тривиальные синтаксические ошибки будут
обнаружены во время начального этапа анализа, но что-либо более глубокое
не будет обнаружено до выполнения).

PL/pgSQL (или, точнее, менеджер SPI) может, кроме того, попытаться
кэшировать план выполнения, связанный с любым конкретным подготовленным
оператором. Если кэшированный план не используется, то новый план
выполнения генерируется при каждом посещении оператора, и текущие
значения параметров (то есть значения переменных PL/pgSQL ) можно
использовать для оптимизации выбранного плана. Если инструкция не имеет
параметров или выполняется много раз, менеджер SPI рассмотрит
возможность создания общего плана, который не зависит от конкретных
значений параметров, и кэширования его для повторного использования.
Обычно это происходит, только если план выполнения не очень чувствителен
к значениям переменных PL/pgSQL, на которые есть ссылки. Если это так,
генерация плана каждый раз является чистой победой. См. PREPARE для
получения дополнительной информации о поведении подготовленных
операторов.

Поскольку PL/pgSQL сохраняет подготовленные операторы и иногда планы
выполнения таким образом, команды SQL, которые появляются
непосредственно в функции PL/pgSQL, должны ссылаться на одни и те же
таблицы и столбцы при каждом выполнении; то есть вы не можете
использовать параметр в качестве имени таблицы или столбца в команде
SQL. Чтобы обойти это ограничение, вы можете создавать динамические
команды с помощью оператора PL/pgSQL EXECUTE — ценой выполнения нового
анализа синтаксического анализа и создания нового плана выполнения при
каждом выполнении.

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

Если одна и та же функция используется в качестве триггера для
нескольких таблиц, PL/pgSQL подготавливает и кэширует операторы
независимо для каждой такой таблицы, то есть существует кэш для каждой
функции триггера и комбинации таблиц, а не только для каждой функции.
Это облегчает некоторые проблемы с различными типами данных; например,
триггерная функция сможет успешно работать со столбцом с именем key даже
если она имеет разные типы в разных таблицах.

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

Кэширование операторов иногда может оказать неожиданное влияние на
интерпретацию чувствительных ко времени значений. Например, есть разница
между тем, что делают эти две функции:

CREATE FUNCTION logfunc1(logtxt text) RETURNS void AS $$
    BEGIN
        INSERT INTO logtable VALUES (logtxt, 'now');
    END;
$$ LANGUAGE plpgsql;

и:

CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
    DECLARE
        curtime timestamp;
    BEGIN
        curtime := 'now';
        INSERT INTO logtable VALUES (logtxt, curtime);
    END;
$$ LANGUAGE plpgsql;

В случае logfunc1 главный анализатор QHB знает, анализируя INSERT
что строка ’now’ должна интерпретироваться как timestamp, потому что
целевой столбец logtable относится к этому типу. Таким образом, ’now’
будет преобразовано в постоянную timestamp при анализе INSERT, а затем
будет использоваться во всех logfunc1 в течение всего времени сеанса.
Излишне говорить, что это не то, что хотел программист. Лучшая идея —
использовать функцию now() или current_timestamp.

В случае logfunc2 основной анализатор QHB не знает, каким должен
быть тип ’now’ и поэтому возвращает значение данных типа text содержащее
строку now. Во время последующего присвоения локальной переменной
curtime интерпретатор PL/pgSQL преобразует эту строку в тип timestamp
, вызывая функции text_out и timestamp_in для преобразования. Таким
образом, вычисленная метка времени обновляется при каждом выполнении,
как того ожидает программист. Несмотря на то, что это работает должным
образом, это не очень эффективно, поэтому использование функции now()
все равно было бы лучшей идеей.

Советы по разработке на PL/pgSQL

Хороший способ разработки в PL/pgSQL — использовать выбранный вами
текстовый редактор для создания своих функций, а в другом окне
использовать psql для загрузки и тестирования этих функций. Если вы
делаете это таким образом, хорошей идеей будет написать функцию,
используя CREATE OR REPLACE FUNCTION. Таким образом, вы можете просто
перезагрузить файл, чтобы обновить определение функции. Например:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $$
          ....
$$ LANGUAGE plpgsql;

Во время работы psql вы можете загрузить или перезагрузить такой файл
определения функции с помощью:

i filename.sql

а затем немедленно выполните команды SQL для проверки функции.

Еще один хороший способ разработки на PL/pgSQL — это инструмент для
доступа к базе данных с графическим интерфейсом, который облегчает
разработку на процедурном языке. Одним из примеров такого инструмента
является pgAdmin, хотя существуют и другие. Эти инструменты часто
предоставляют удобные функции, такие как экранирование одинарных кавычек
и упрощение воссоздания и отладки функций.

Обработка кавычек

Код функции PL/pgSQL указывается в CREATE FUNCTION как строковый
литерал. Если вы пишете строковый литерал обычным способом с окружающими
одинарными кавычками, то любые одинарные кавычки внутри тела функции
должны быть удвоены; аналогично, любые обратные слеши должны быть
удвоены (при условии использования синтаксиса escape-строки). Удвоение
кавычек в лучшем случае утомительно, а в более сложных случаях код может
стать совершенно непонятным, потому что вы можете легко найти себе
полдюжины или более соседних кавычек. Вместо этого рекомендуется
написать тело функции в виде строкового литерала, заключенного в
«кавычки» (см. раздел Строковые константы с экранированием знаками доллара). При способе с использованием
знаков доллара вы никогда не удваиваете любые кавычки, а вместо этого
стараетесь выбирать разные разделители долларовых кавычек для каждого
необходимого уровня вложенности. Например, можно написать команду
CREATE FUNCTION как:

CREATE OR REPLACE FUNCTION testfunc(integer) RETURNS integer AS $PROC$
          ....
$PROC$ LANGUAGE plpgsql;

В рамках этого вы можете использовать кавычки для простых литеральных
строк в командах SQL и $$ для разделения фрагментов команд SQL, которые
вы собираете в виде строк. Если вам нужно заключить в кавычки текст, $$,
вы можете использовать $Q$ и так далее.

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

одинарные кавычки

  • Чтобы начать и закончить тело функции, например:
CREATE FUNCTION foo() RETURNS integer AS '
          ....
' LANGUAGE plpgsql;

Везде в теле функции, заключенном в одинарные кавычки, кавычки
должны появляться парами.

парные кавычки

  • Для строковых литералов внутри тела функции, например:
a_output := ''Blah'';
SELECT * FROM users WHERE f_name=''foobar'';

При подходе «экранирование знаками доллара» вы просто пишете:

a_output := 'Blah';
SELECT * FROM users WHERE f_name='foobar';

это именно то, что анализатор PL/pgSQL будет видеть в любом
случае.

4 кавычки

  • Когда вам нужна одиночная кавычка в строковой константе внутри тела
    функции, например:
a_output := a_output || '' AND name LIKE ''''foobar'''' AND xyz''

Значение, добавленное к a_output, будет: AND name LIKE ’foobar’
AND xyz.

В подходе «экранирование знаками доллара» вы написали бы:

a_output := a_output || $$ AND name LIKE 'foobar' AND xyz$$

будьте осторожны, чтобы любые разделители долларовых кавычек вокруг
этого не были просто $$.

6 кавычек

  • Когда одна кавычка в строке внутри тела функции находится рядом с
    концом этой строковой константы, например:
a_output := a_output || '' AND name LIKE ''''foobar''''''

Значение, добавленное к a_output будет: AND name LIKE ’foobar’.

В подходе «экранирование знаками доллара» это становится:

a_output := a_output || $$ AND name LIKE 'foobar'$$

10 кавычек

  • Когда вам нужно две одинарные кавычки в строковой константе (на
    которую приходится 8 кавычек), и это рядом с концом этой строковой
    константы (еще 2). Это, вероятно, понадобится вам, только если вы
    пишете функцию, которая генерирует другие функции, как в
    примере 10. Например:
a_output := a_output || '' if v_'' ||
    referrer_keys.kind || '' like ''''''''''
    || referrer_keys.key_string || ''''''''''
    then return ''''''  || referrer_keys.referrer_type
    || ''''''; end if;'';

Значение a_output тогда будет:

if v_... like ''...'' then return ''...''; end if;

В подходе «экранирование знаками доллара» это становится:

a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
    || referrer_keys.key_string || $$'
    then return '$$  || referrer_keys.referrer_type
    || $$'; end if;$$;

где мы предполагаем, что нам нужно только поставить одинарные кавычки в
a_output, потому что он будет повторно цитирован перед использованием.

Дополнительные проверки во время компиляции и во время выполнения

Чтобы помочь пользователю найти примеры простых, но распространенных
проблем до того, как они причинят вред, PL/pgSQL предоставляет
дополнительные checks. Когда они включены, в зависимости от
конфигурации они могут использоваться для выдачи WARNING или ERROR во
время компиляции функции. Функция, получившая WARNING может быть
выполнена без выдачи дополнительных сообщений, поэтому рекомендуется
выполнять тестирование в отдельной среде разработки.

При необходимости установка plpgsql.extra_warnings или
plpgsql.extra_errors в значение «all» рекомендуется в средах разработки
и/или тестирования.

Эти дополнительные проверки включены через переменные конфигурации
plpgsql.extra_warnings для предупреждений и plpgsql.extra_errors для
ошибок. Оба могут быть установлены в список проверок, разделенных
запятыми, «none» или «all». По умолчанию установлено значение «none».
В настоящее время список доступных проверок включает в себя:

shadowed_variables

  • Проверяет, скрывает ли объявление ранее определенную переменную.

strict_multi_assignment

  • Некоторые команды PL/pgSQL позволяют присваивать значения более
    чем одной переменной одновременно, например, SELECT INTO. Как
    правило, число целевых переменных и количество исходных переменных
    должны совпадать, хотя PL/pgSQL будет использовать NULL для
    пропущенных значений, а дополнительные переменные игнорируются.
    Включение этой проверки приведет к тому, что PL/pgSQL будет
    выдавать WARNING или ERROR всякий раз, когда число целевых
    переменных и количество исходных переменных различаются.

too_many_rows

  • Включение этой проверки заставит PL/pgSQL проверять, возвращает ли
    данный запрос более одной строки, когда используется предложение
    INTO. Поскольку оператор INTO будет когда-либо использовать только
    одну строку, запрос, возвращающий несколько строк, как правило, либо
    неэффективен, либо недетерминирован, и, следовательно, вероятно,
    является ошибкой.

В следующем примере показано влияние plpgsql.extra_warnings
установленного на shadowed_variables:

SET plpgsql.extra_warnings TO 'shadowed_variables';

CREATE FUNCTION foo(f1 int) RETURNS int AS $$
DECLARE
f1 int;
BEGIN
RETURN f1;
END
$$ LANGUAGE plpgsql;
WARNING:  variable "f1" shadows a previously defined variable
LINE 3: f1 int;
        ^
CREATE FUNCTION

В приведенном ниже примере показаны эффекты установки
plpgsql.extra_warnings в значение strict_multi_assignment:

SET plpgsql.extra_warnings TO 'strict_multi_assignment';

CREATE OR REPLACE FUNCTION public.foo()
 RETURNS void
 LANGUAGE plpgsql
AS $$
DECLARE
  x int;
  y int;
BEGIN
  SELECT 1 INTO x, y;
  SELECT 1, 2 INTO x, y;
  SELECT 1, 2, 3 INTO x, y;
END;
$$;

SELECT foo();
WARNING:  number of source and target fields in assignment does not match
DETAIL:  strict_multi_assignment check of extra_warnings is active.
HINT:  Make sure the query returns the exact list of columns.
WARNING:  number of source and target fields in assignment does not match
DETAIL:  strict_multi_assignment check of extra_warnings is active.
HINT:  Make sure the query returns the exact list of columns.

 foo
-----

(1 row)

Портирование из Oracle PL/SQL

В этом разделе объясняются различия между языком PL/pgSQL QHB и
языком PL/SQL Oracle, чтобы помочь разработчикам, которые переносят
приложения из Oracle® в QHB.

PL/pgSQL во многом похож на PL/SQL. Это блочно-структурированный
императивный язык, и все переменные должны быть объявлены. Присвоения,
циклы и условия аналогичны. Основные различия, которые следует учитывать
при переносе с PL/SQL на PL/pgSQL:

  • Если имя, используемое в команде SQL, может быть либо именем столбца
    таблицы, либо ссылкой на переменную функции, PL/SQL обрабатывает
    его как имя столбца. Это соответствует поведению PL/pgSQL
    plpgsql.variable_conflict = use_column, которое не является
    значением по умолчанию, как описано в разделе Подстановка переменных. Во-первых,
    во-первых, часто лучше избегать таких неоднозначностей, но если вам
    нужно портировать большой объем кода, который зависит от этого
    поведения, настройка variable_conflict может быть лучшим решением.

  • В QHB тело функции должно быть записано как строковый
    литерал. Поэтому вам нужно использовать знаки доллара или
    экранировать одинарные кавычки в теле функции.
    (См. раздел Обработка кавычек).

  • Имена типов данных часто нуждаются в переводе. Например, в Oracle
    строковые значения обычно объявляются как имеющие тип varchar2,
    который является нестандартным типом SQL. В QHB вместо этого
    используйте тип varchar или text. Аналогично, замените тип number на
    numeric или используйте другой тип числовых данных, если есть более
    подходящий.

  • Вместо пакетов используйте схемы для организации ваших функций в
    группы.

  • Поскольку пакетов нет, переменных уровня пакета тоже нет. Это
    несколько раздражает. Вместо этого вы можете сохранить состояние
    сеанса во временных таблицах.

  • Целочисленные циклы FOR с REVERSE работают по-разному: PL/SQL
    ведет обратный отсчет от второго числа к первому, а PL/pgSQL ведет
    обратный отсчет от первого числа ко второму, что требует замены
    границ цикла при переносе. Эта несовместимость вызывает сожаление,
    но вряд ли изменится. (См. раздел FOR (целочисленный вариант)).

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

  • Существуют различные нотационные различия для использования
    переменных курсора.

Примеры портирования

В примере 9 показано, как перенести простую функцию из PL/SQL в PL/pgSQL.

Пример 9. Портирование простой функции из PL/SQL в PL/pgSQL

Вот функция Oracle PL/SQL :

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar2,
                                                  v_version varchar2)
RETURN varchar2 IS
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
/
show errors;

Давайте рассмотрим эту функцию и посмотрим на различия по сравнению с
PL/pgSQL :

  • Имя типа varchar2 должно быть изменено на varchar или text. В
    примерах в этом разделе мы будем использовать varchar, но text
    часто является лучшим выбором, если вам не нужны определенные
    ограничения длины строки.

  • Ключевое слово RETURN в прототипе функции (а не в теле функции)
    становится RETURNS в QHB. Кроме того, IS становится AS, и
    вам нужно добавить предложение LANGUAGE, поскольку PL/pgSQL не
    является единственным возможным языком функций.

  • В QHB тело функции считается строковым литералом, поэтому
    вокруг него нужно использовать кавычки или знаки доллара. Это
    заменяет прекращение / в подходе Oracle.

  • Команда show errors не существует в QHB и не нужна, поскольку
    ошибки сообщаются автоматически.

Вот как будет выглядеть эта функция при портировании на QHB:

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
                                                  v_version varchar)
RETURNS varchar AS $$
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
$$ LANGUAGE plpgsql;

В примере 10 показано, как портировать функцию, которая создает
другую функцию, и как справиться с возникающими проблемами цитирования.

Пример 10. Портирование функции, которая создает другую функцию из
PL/SQL в PL/pgSQL

Следующая процедура извлекает строки из SELECT и строит большую функцию
с результатами в операторах IF для эффективности.

Это версия Oracle:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
    CURSOR referrer_keys IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_cmd VARCHAR(4000);
BEGIN
    func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR2,
                 v_domain IN VARCHAR2, v_url IN VARCHAR2) RETURN VARCHAR2 IS BEGIN';

    FOR referrer_key IN referrer_keys LOOP
        func_cmd := func_cmd ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ''' || referrer_key.key_string
          || ''' THEN RETURN ''' || referrer_key.referrer_type
          || '''; END IF;';
    END LOOP;

    func_cmd := func_cmd || ' RETURN NULL; END;';

    EXECUTE IMMEDIATE func_cmd;
END;
/
show errors;

Вот как эта функция может оказаться в QHB:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc() AS $func$
DECLARE
    referrer_keys CURSOR IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_body text;
    func_cmd text;
BEGIN
    func_body := 'BEGIN';

    FOR referrer_key IN referrer_keys LOOP
        func_body := func_body ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ' || quote_literal(referrer_key.key_string)
          || ' THEN RETURN ' || quote_literal(referrer_key.referrer_type)
          || '; END IF;' ;
    END LOOP;

    func_body := func_body || ' RETURN NULL; END;';

    func_cmd :=
      'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host varchar,
                                                        v_domain varchar,
                                                        v_url varchar)
        RETURNS varchar AS '
      || quote_literal(func_body)
      || ' LANGUAGE plpgsql;' ;

    EXECUTE func_cmd;
END;
$func$ LANGUAGE plpgsql;

Обратите внимание, что тело функции quote_literal отдельно и передается
через quote_literal чтобы удвоить любые кавычки в нем. Этот метод
необходим, потому что мы не можем безопасно использовать долларовые
кавычки для определения новой функции: мы не знаем наверняка, какие
строки будут интерполированы из поля referrer_key.key_string. (Здесь
мы предполагаем, что referrer_key.kind всегда можно доверять, чтобы он
всегда был host, domain или url, но referrer_key.key_string может
быть чем угодно, в частности он может содержать знаки доллара). Эта
функция на самом деле является улучшением оригинала Oracle, потому что
он не будет генерировать неработающий код, когда
referrer_key.key_string или referrer_key.referrer_type содержат
кавычки.

В примере 11 показано, как перенести функцию с параметрами OUT и
обработкой строк. QHB не имеет встроенной функции instr, но вы
можете создать ее, используя комбинацию других функций. В разделе Приложение
описана реализация instr в PL/pgSQL, которую вы можете
использовать для упрощения переноса.

Пример 11. Перенос процедуры с параметрами String Manipulation и OUT
из PL/SQL в PL/pgSQL

Следующая процедура Oracle PL/SQL используется для анализа URL-адреса
и возврата нескольких элементов (хост, путь и запрос).

Это версия Oracle:

CREATE OR REPLACE PROCEDURE cs_parse_url(
    v_url IN VARCHAR2,
    v_host OUT VARCHAR2,  -- This will be passed back
    v_path OUT VARCHAR2,  -- This one too
    v_query OUT VARCHAR2) -- And this one
IS
    a_pos1 INTEGER;
    a_pos2 INTEGER;
BEGIN
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//');

    IF a_pos1 = 0 THEN
        RETURN;
    END IF;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    IF a_pos2 = 0 THEN
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        RETURN;
    END IF;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    IF a_pos1 = 0 THEN
        v_path := substr(v_url, a_pos2);
        RETURN;
    END IF;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
END;
/
show errors;

Вот возможный перевод на PL/pgSQL :

CREATE OR REPLACE FUNCTION cs_parse_url(
    v_url IN VARCHAR,
    v_host OUT VARCHAR,  -- This will be passed back
    v_path OUT VARCHAR,  -- This one too
    v_query OUT VARCHAR) -- And this one
AS $$
DECLARE
    a_pos1 INTEGER;
    a_pos2 INTEGER;
BEGIN
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//');

    IF a_pos1 = 0 THEN
        RETURN;
    END IF;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    IF a_pos2 = 0 THEN
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        RETURN;
    END IF;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    IF a_pos1 = 0 THEN
        v_path := substr(v_url, a_pos2);
        RETURN;
    END IF;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
END;
$$ LANGUAGE plpgsql;

Эту функцию можно использовать так:

SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');

В примере 12 показано, как портировать процедуру, которая использует
многочисленные функции, характерные для Oracle.

Пример 12. Портирование процедуры из PL/SQL в PL/pgSQL

Версия Oracle:

CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS
    a_running_job_count INTEGER;
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        COMMIT; -- free lock
        raise_application_error(-20000,
                 'Unable to create a new job: a job is currently running.');
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN dup_val_on_index THEN NULL; -- don't worry if it already exists
    END;
    COMMIT;
END;
/
show errors

Вот как мы могли бы перенести эту процедуру в PL/pgSQL :

CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id integer) AS $$
DECLARE
    a_running_job_count integer;
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        COMMIT; -- free lock
        RAISE EXCEPTION 'Unable to create a new job: a job is currently running'; -- [^1]
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN unique_violation THEN -- [^2]
            -- don't worry if it already exists
    END;
    COMMIT;
END;
$$ LANGUAGE plpgsql;

Другие вещи для наблюдения

В этом разделе объясняются некоторые другие вещи, на которые нужно
обратить внимание при портировании функций Oracle PL/SQL на QHB.

Неявный откат после исключений

В PL/pgSQL, когда исключение отлавливается предложением EXCEPTION,
все изменения в базе данных, так как блок BEGIN автоматически
откатывается. То есть поведение эквивалентно тому, что вы получите в
Oracle:

BEGIN
    SAVEPOINT s1;
    ... code here ...
EXCEPTION
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
END;

Если вы переводите процедуру Oracle, которая использует SAVEPOINT и
ROLLBACK TO в этом стиле, ваша задача проста: просто опустите SAVEPOINT
и ROLLBACK TO. Если у вас есть процедура, в которой SAVEPOINT и
ROLLBACK TO используются по-разному, вам понадобится определенная мысль.

EXECUTE

Версия EXECUTE для PL/pgSQL работает аналогично версии PL/SQL, но
вы должны помнить, что используйте quote_literal и quote_ident как
описано в разделе Выполнение динамических команд. Конструкции типа EXECUTE 'SELECT * FROM $1';
не будет надежно работать, если вы не используете эти функции.

Оптимизация функций PL/pgSQL

QHB предоставляет вам два модификатора создания функции для
оптимизации выполнения: « изменчивость » (всегда ли функция возвращает
один и тот же результат, если даны одни и те же аргументы) и «строгость»
(возвращает ли функция ноль, если какой-либо аргумент равен нулю).
Обратитесь к справочной странице CREATE FUNCTION за подробностями.

При использовании этих атрибутов оптимизации ваш оператор CREATE
FUNCTION
может выглядеть примерно так:

CREATE FUNCTION foo(...) RETURNS integer AS $$
...
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

Приложение

Этот раздел содержит код для набора Oracle-совместимых функций instr
которые вы можете использовать для упрощения процесса переноса.

-
-- instr functions that mimic Oracle's counterpart
-- Syntax: instr(string1, string2 [, n [, m]])
-- where [] denotes optional parameters.
--
-- Search string1, beginning at the nth character, for the mth occurrence
-- of string2.  If n is negative, search backwards, starting at the abs(n)'th
-- character from the end of string1.
-- If n is not passed, assume 1 (search starts at first character).
-- If m is not passed, assume 1 (find first occurrence).
-- Returns starting index of string2 in string1, or 0 if string2 is not found.
--

CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$
BEGIN
    RETURN instr($1, $2, 1);
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;


CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
                      beg_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN
        temp_str := substring(string FROM beg_index);
        pos := position(string_to_search_for IN temp_str);

        IF pos = 0 THEN
            RETURN 0;
        ELSE
            RETURN pos + beg_index - 1;
        END IF;
    ELSIF beg_index < 0 THEN
        ss_length := char_length(string_to_search_for);
        length := char_length(string);
        beg := length + 1 + beg_index;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            IF string_to_search_for = temp_str THEN
                RETURN beg;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;


CREATE FUNCTION instr(string varchar, string_to_search_for varchar,
                      beg_index integer, occur_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    occur_number integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    i integer;
    length integer;
    ss_length integer;
BEGIN
    IF occur_index <= 0 THEN
        RAISE 'argument ''%'' is out of range', occur_index
          USING ERRCODE = '22003';
    END IF;

    IF beg_index > 0 THEN
        beg := beg_index - 1;
        FOR i IN 1..occur_index LOOP
            temp_str := substring(string FROM beg + 1);
            pos := position(string_to_search_for IN temp_str);
            IF pos = 0 THEN
                RETURN 0;
            END IF;
            beg := beg + pos;
        END LOOP;

        RETURN beg;
    ELSIF beg_index < 0 THEN
        ss_length := char_length(string_to_search_for);
        length := char_length(string);
        beg := length + 1 + beg_index;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            IF string_to_search_for = temp_str THEN
                occur_number := occur_number + 1;
                IF occur_number = occur_index THEN
                    RETURN beg;
                END IF;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

Содержание

  1. Postgresql query returned no rows
  2. 43.5.1. Assignment
  3. 43.5.2. Executing SQL Commands
  4. 43.5.3. Executing a Command with a Single-Row Result
  5. 43.5.4. Executing Dynamic Commands
  6. 43.5.5. Obtaining the Result Status
  7. 43.5.6. Doing Nothing At All
  8. Submit correction

Postgresql query returned no rows

In this section and the following ones, we describe all the statement types that are explicitly understood by PL/pgSQL . Anything not recognized as one of these statement types is presumed to be an SQL command and is sent to the main database engine to execute, as described in Section 43.5.2.

43.5.1. Assignment

An assignment of a value to a PL/pgSQL variable is written as:

As explained previously, the expression in such a statement is evaluated by means of an SQL SELECT command sent to the main database engine. The expression must yield a single value (possibly a row value, if the variable is a row or record variable). The target variable can be a simple variable (optionally qualified with a block name), a field of a row or record target, or an element or slice of an array target. Equal ( = ) can be used instead of PL/SQL-compliant := .

If the expression’s result data type doesn’t match the variable’s data type, the value will be coerced as though by an assignment cast (see Section 10.4). If no assignment cast is known for the pair of data types involved, the PL/pgSQL interpreter will attempt to convert the result value textually, that is by applying the result type’s output function followed by the variable type’s input function. Note that this could result in run-time errors generated by the input function, if the string form of the result value is not acceptable to the input function.

43.5.2. Executing SQL Commands

In general, any SQL command that does not return rows can be executed within a PL/pgSQL function just by writing the command. For example, you could create and fill a table by writing

If the command does return rows (for example SELECT , or INSERT / UPDATE / DELETE with RETURNING ), there are two ways to proceed. When the command will return at most one row, or you only care about the first row of output, write the command as usual but add an INTO clause to capture the output, as described in Section 43.5.3. To process all of the output rows, write the command as the data source for a FOR loop, as described in Section 43.6.6.

Usually it is not sufficient just to execute statically-defined SQL commands. Typically you’ll want a command to use varying data values, or even to vary in more fundamental ways such as by using different table names at different times. Again, there are two ways to proceed depending on the situation.

PL/pgSQL variable values can be automatically inserted into optimizable SQL commands, which are SELECT , INSERT , UPDATE , DELETE , and certain utility commands that incorporate one of these, such as EXPLAIN and CREATE TABLE . AS SELECT . In these commands, any PL/pgSQL variable name appearing in the command text is replaced by a query parameter, and then the current value of the variable is provided as the parameter value at run time. This is exactly like the processing described earlier for expressions; for details see Section 43.11.1.

When executing an optimizable SQL command in this way, PL/pgSQL may cache and re-use the execution plan for the command, as discussed in Section 43.11.2.

Non-optimizable SQL commands (also called utility commands) are not capable of accepting query parameters. So automatic substitution of PL/pgSQL variables does not work in such commands. To include non-constant text in a utility command executed from PL/pgSQL , you must build the utility command as a string and then EXECUTE it, as discussed in Section 43.5.4.

EXECUTE must also be used if you want to modify the command in some other way than supplying a data value, for example by changing a table name.

Sometimes it is useful to evaluate an expression or SELECT query but discard the result, for example when calling a function that has side-effects but no useful result value. To do this in PL/pgSQL , use the PERFORM statement:

This executes query and discards the result. Write the query the same way you would write an SQL SELECT command, but replace the initial keyword SELECT with PERFORM . For WITH queries, use PERFORM and then place the query in parentheses. (In this case, the query can only return one row.) PL/pgSQL variables will be substituted into the query just as described above, and the plan is cached in the same way. Also, the special variable FOUND is set to true if the query produced at least one row, or false if it produced no rows (see Section 43.5.5).

One might expect that writing SELECT directly would accomplish this result, but at present the only accepted way to do it is PERFORM . An SQL command that can return rows, such as SELECT , will be rejected as an error unless it has an INTO clause as discussed in the next section.

43.5.3. Executing a Command with a Single-Row Result

The result of an SQL command yielding a single row (possibly of multiple columns) can be assigned to a record variable, row-type variable, or list of scalar variables. This is done by writing the base SQL command and adding an INTO clause. For example,

where target can be a record variable, a row variable, or a comma-separated list of simple variables and record/row fields. PL/pgSQL variables will be substituted into the rest of the command (that is, everything but the INTO clause) just as described above, and the plan is cached in the same way. This works for SELECT , INSERT / UPDATE / DELETE with RETURNING , and certain utility commands that return row sets, such as EXPLAIN . Except for the INTO clause, the SQL command is the same as it would be written outside PL/pgSQL .

Note that this interpretation of SELECT with INTO is quite different from PostgreSQL ‘s regular SELECT INTO command, wherein the INTO target is a newly created table. If you want to create a table from a SELECT result inside a PL/pgSQL function, use the syntax CREATE TABLE . AS SELECT .

If a row variable or a variable list is used as target, the command’s result columns must exactly match the structure of the target as to number and data types, or else a run-time error occurs. When a record variable is the target, it automatically configures itself to the row type of the command’s result columns.

The INTO clause can appear almost anywhere in the SQL command. Customarily it is written either just before or just after the list of select_expressions in a SELECT command, or at the end of the command for other command types. It is recommended that you follow this convention in case the PL/pgSQL parser becomes stricter in future versions.

If STRICT is not specified in the INTO clause, then target will be set to the first row returned by the command, or to nulls if the command returned no rows. (Note that “ the first row ” is not well-defined unless you’ve used ORDER BY .) Any result rows after the first row are discarded. You can check the special FOUND variable (see Section 43.5.5) to determine whether a row was returned:

If the STRICT option is specified, the command must return exactly one row or a run-time error will be reported, either NO_DATA_FOUND (no rows) or TOO_MANY_ROWS (more than one row). You can use an exception block if you wish to catch the error, for example:

Successful execution of a command with STRICT always sets FOUND to true.

For INSERT / UPDATE / DELETE with RETURNING , PL/pgSQL reports an error for more than one returned row, even when STRICT is not specified. This is because there is no option such as ORDER BY with which to determine which affected row should be returned.

If print_strict_params is enabled for the function, then when an error is thrown because the requirements of STRICT are not met, the DETAIL part of the error message will include information about the parameters passed to the command. You can change the print_strict_params setting for all functions by setting plpgsql.print_strict_params , though only subsequent function compilations will be affected. You can also enable it on a per-function basis by using a compiler option, for example:

On failure, this function might produce an error message such as

The STRICT option matches the behavior of Oracle PL/SQL’s SELECT INTO and related statements.

43.5.4. Executing Dynamic Commands

Oftentimes you will want to generate dynamic commands inside your PL/pgSQL functions, that is, commands that will involve different tables or different data types each time they are executed. PL/pgSQL ‘s normal attempts to cache plans for commands (as discussed in Section 43.11.2) will not work in such scenarios. To handle this sort of problem, the EXECUTE statement is provided:

where command-string is an expression yielding a string (of type text ) containing the command to be executed. The optional target is a record variable, a row variable, or a comma-separated list of simple variables and record/row fields, into which the results of the command will be stored. The optional USING expressions supply values to be inserted into the command.

No substitution of PL/pgSQL variables is done on the computed command string. Any required variable values must be inserted in the command string as it is constructed; or you can use parameters as described below.

Also, there is no plan caching for commands executed via EXECUTE . Instead, the command is always planned each time the statement is run. Thus the command string can be dynamically created within the function to perform actions on different tables and columns.

The INTO clause specifies where the results of an SQL command returning rows should be assigned. If a row variable or variable list is provided, it must exactly match the structure of the command’s results; if a record variable is provided, it will configure itself to match the result structure automatically. If multiple rows are returned, only the first will be assigned to the INTO variable(s). If no rows are returned, NULL is assigned to the INTO variable(s). If no INTO clause is specified, the command results are discarded.

If the STRICT option is given, an error is reported unless the command produces exactly one row.

The command string can use parameter values, which are referenced in the command as $1 , $2 , etc. These symbols refer to values supplied in the USING clause. This method is often preferable to inserting data values into the command string as text: it avoids run-time overhead of converting the values to text and back, and it is much less prone to SQL-injection attacks since there is no need for quoting or escaping. An example is:

Note that parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually. For example, if the preceding query needed to be done against a dynamically selected table, you could do this:

A cleaner approach is to use format() ‘s %I specification to insert table or column names with automatic quoting:

(This example relies on the SQL rule that string literals separated by a newline are implicitly concatenated.)

Another restriction on parameter symbols is that they only work in optimizable SQL commands ( SELECT , INSERT , UPDATE , DELETE , MERGE , and certain commands containing one of these). In other statement types (generically called utility statements), you must insert values textually even if they are just data values.

An EXECUTE with a simple constant command string and some USING parameters, as in the first example above, is functionally equivalent to just writing the command directly in PL/pgSQL and allowing replacement of PL/pgSQL variables to happen automatically. The important difference is that EXECUTE will re-plan the command on each execution, generating a plan that is specific to the current parameter values; whereas PL/pgSQL may otherwise create a generic plan and cache it for re-use. In situations where the best plan depends strongly on the parameter values, it can be helpful to use EXECUTE to positively ensure that a generic plan is not selected.

SELECT INTO is not currently supported within EXECUTE ; instead, execute a plain SELECT command and specify INTO as part of the EXECUTE itself.

The PL/pgSQL EXECUTE statement is not related to the EXECUTE SQL statement supported by the PostgreSQL server. The server’s EXECUTE statement cannot be used directly within PL/pgSQL functions (and is not needed).

Example 43.1. Quoting Values in Dynamic Queries

When working with dynamic commands you will often have to handle escaping of single quotes. The recommended method for quoting fixed text in your function body is dollar quoting. (If you have legacy code that does not use dollar quoting, please refer to the overview in Section 43.12.1, which can save you some effort when translating said code to a more reasonable scheme.)

Dynamic values require careful handling since they might contain quote characters. An example using format() (this assumes that you are dollar quoting the function body so quote marks need not be doubled):

It is also possible to call the quoting functions directly:

This example demonstrates the use of the quote_ident and quote_literal functions (see Section 9.4). For safety, expressions containing column or table identifiers should be passed through quote_ident before insertion in a dynamic query. Expressions containing values that should be literal strings in the constructed command should be passed through quote_literal . These functions take the appropriate steps to return the input text enclosed in double or single quotes respectively, with any embedded special characters properly escaped.

Because quote_literal is labeled STRICT , it will always return null when called with a null argument. In the above example, if newvalue or keyvalue were null, the entire dynamic query string would become null, leading to an error from EXECUTE . You can avoid this problem by using the quote_nullable function, which works the same as quote_literal except that when called with a null argument it returns the string NULL . For example,

If you are dealing with values that might be null, you should usually use quote_nullable in place of quote_literal .

As always, care must be taken to ensure that null values in a query do not deliver unintended results. For example the WHERE clause

will never succeed if keyvalue is null, because the result of using the equality operator = with a null operand is always null. If you wish null to work like an ordinary key value, you would need to rewrite the above as

(At present, IS NOT DISTINCT FROM is handled much less efficiently than = , so don’t do this unless you must. See Section 9.2 for more information on nulls and IS DISTINCT .)

Note that dollar quoting is only useful for quoting fixed text. It would be a very bad idea to try to write this example as:

because it would break if the contents of newvalue happened to contain $$ . The same objection would apply to any other dollar-quoting delimiter you might pick. So, to safely quote text that is not known in advance, you must use quote_literal , quote_nullable , or quote_ident , as appropriate.

Dynamic SQL statements can also be safely constructed using the format function (see Section 9.4.1). For example:

%I is equivalent to quote_ident , and %L is equivalent to quote_nullable . The format function can be used in conjunction with the USING clause:

This form is better because the variables are handled in their native data type format, rather than unconditionally converting them to text and quoting them via %L . It is also more efficient.

A much larger example of a dynamic command and EXECUTE can be seen in Example 43.10, which builds and executes a CREATE FUNCTION command to define a new function.

43.5.5. Obtaining the Result Status

There are several ways to determine the effect of a command. The first method is to use the GET DIAGNOSTICS command, which has the form:

This command allows retrieval of system status indicators. CURRENT is a noise word (but see also GET STACKED DIAGNOSTICS in Section 43.6.8.1). Each item is a key word identifying a status value to be assigned to the specified variable (which should be of the right data type to receive it). The currently available status items are shown in Table 43.1. Colon-equal ( := ) can be used instead of the SQL-standard = token. An example:

Table 43.1. Available Diagnostics Items

Name Type Description
ROW_COUNT bigint the number of rows processed by the most recent SQL command
PG_CONTEXT text line(s) of text describing the current call stack (see Section 43.6.9)

The second method to determine the effects of a command is to check the special variable named FOUND , which is of type boolean . FOUND starts out false within each PL/pgSQL function call. It is set by each of the following types of statements:

A SELECT INTO statement sets FOUND true if a row is assigned, false if no row is returned.

A PERFORM statement sets FOUND true if it produces (and discards) one or more rows, false if no row is produced.

UPDATE , INSERT , DELETE , and MERGE statements set FOUND true if at least one row is affected, false if no row is affected.

A FETCH statement sets FOUND true if it returns a row, false if no row is returned.

A MOVE statement sets FOUND true if it successfully repositions the cursor, false otherwise.

A FOR or FOREACH statement sets FOUND true if it iterates one or more times, else false. FOUND is set this way when the loop exits; inside the execution of the loop, FOUND is not modified by the loop statement, although it might be changed by the execution of other statements within the loop body.

RETURN QUERY and RETURN QUERY EXECUTE statements set FOUND true if the query returns at least one row, false if no row is returned.

Other PL/pgSQL statements do not change the state of FOUND . Note in particular that EXECUTE changes the output of GET DIAGNOSTICS , but does not change FOUND .

FOUND is a local variable within each PL/pgSQL function; any changes to it affect only the current function.

43.5.6. Doing Nothing At All

Sometimes a placeholder statement that does nothing is useful. For example, it can indicate that one arm of an if/then/else chain is deliberately empty. For this purpose, use the NULL statement:

For example, the following two fragments of code are equivalent:

Which is preferable is a matter of taste.

In Oracle’s PL/SQL, empty statement lists are not allowed, and so NULL statements are required for situations such as this. PL/pgSQL allows you to just write nothing, instead.

Prev Up Next
43.4. Expressions Home 43.6. Control Structures

Submit correction

If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.

Copyright © 1996-2022 The PostgreSQL Global Development Group

Источник

This tutorial introduces the basics of accessing a relational database with
Go and the database/sql package in its standard library.

You’ll get the most out of this tutorial if you have a basic familiarity with
Go and its tooling. If this is your first exposure to Go, please see
Tutorial: Get started with Go
for a quick introduction.

The database/sql package you’ll
be using includes types and functions for connecting to databases, executing
transactions, canceling an operation in progress, and more. For more details
on using the package, see
Accessing databases.

In this tutorial, you’ll create a database, then write code to access the
database. Your example project will be a repository of data about vintage
jazz records.

In this tutorial, you’ll progress through the following sections:

  1. Create a folder for your code.
  2. Set up a database.
  3. Import the database driver.
  4. Get a database handle and connect.
  5. Query for multiple rows.
  6. Query for a single row.
  7. Add data.

Note: For other tutorials, see Tutorials.

Prerequisites

  • An installation of the MySQL
    relational database management system (DBMS).
  • An installation of Go. For installation instructions, see
    Installing Go.
  • A tool to edit your code. Any text editor you have will work fine.
  • A command terminal. Go works well using any terminal on Linux and Mac,
    and on PowerShell or cmd in Windows.

Create a folder for your code

To begin, create a folder for the code you’ll write.

  1. Open a command prompt and change to your home directory.

    On Linux or Mac:

    $ cd
    

    On Windows:

    C:> cd %HOMEPATH%
    

    For the rest of the tutorial we will show a $ as the prompt. The
    commands we use will work on Windows too.

  2. From the command prompt, create a directory for your code called
    data-access.

    $ mkdir data-access
    $ cd data-access
    
  3. Create a module in which you can manage dependencies you will add during
    this tutorial.

    Run the go mod init command, giving it your new code’s module path.

    $ go mod init example/data-access
    go: creating new go.mod: module example/data-access
    

    This command creates a go.mod file in which dependencies you add will be
    listed for tracking. For more, be sure to see
    Managing dependencies.

    Note: In actual development, you’d specify a module path that’s
    more specific to your own needs. For more, see
    Managing dependencies.

Next, you’ll create a database.

Set up a database

In this step, you’ll create the database you’ll be working with. You’ll use
the CLI for the DBMS itself to create the database and table, as well as to
add data.

You’ll be creating a database with data about vintage jazz recordings on vinyl.

The code here uses the MySQL CLI,
but most DBMSes have their own CLI with similar features.

  1. Open a new command prompt.

  2. At the command line, log into your DBMS, as in the following example for
    MySQL.

    $ mysql -u root -p
    Enter password:
    
    mysql>
    
  3. At the mysql command prompt, create a database.

    mysql> create database recordings;
    
  4. Change to the database you just created so you can add tables.

    mysql> use recordings;
    Database changed
    
  5. In your text editor, in the data-access folder, create a file called
    create-tables.sql to hold SQL script for adding tables.

  6. Into the file, paste the following SQL code, then save the file.

    DROP TABLE IF EXISTS album;
    CREATE TABLE album (
      id         INT AUTO_INCREMENT NOT NULL,
      title      VARCHAR(128) NOT NULL,
      artist     VARCHAR(255) NOT NULL,
      price      DECIMAL(5,2) NOT NULL,
      PRIMARY KEY (`id`)
    );
    
    INSERT INTO album
      (title, artist, price)
    VALUES
      ('Blue Train', 'John Coltrane', 56.99),
      ('Giant Steps', 'John Coltrane', 63.99),
      ('Jeru', 'Gerry Mulligan', 17.99),
      ('Sarah Vaughan', 'Sarah Vaughan', 34.98);
    

    In this SQL code, you:

    • Delete (drop) a table called album. Executing this command first makes
      it easier for you to re-run the script later if you want to start over
      with the table.

    • Create an album table with four columns: title, artist, and price.
      Each row’s id value is created automatically by the DBMS.

    • Add four rows with values.

  7. From the mysql command prompt, run the script you just created.

    You’ll use the source command in the following form:

    mysql> source /path/to/create-tables.sql
    
  8. At your DBMS command prompt, use a SELECT statement to verify you’ve
    successfully created the table with data.

    mysql> select * from album;
    +----+---------------+----------------+-------+
    | id | title         | artist         | price |
    +----+---------------+----------------+-------+
    |  1 | Blue Train    | John Coltrane  | 56.99 |
    |  2 | Giant Steps   | John Coltrane  | 63.99 |
    |  3 | Jeru          | Gerry Mulligan | 17.99 |
    |  4 | Sarah Vaughan | Sarah Vaughan  | 34.98 |
    +----+---------------+----------------+-------+
    4 rows in set (0.00 sec)
    

Next, you’ll write some Go code to connect so you can query.

Find and import a database driver

Now that you’ve got a database with some data, get your Go code started.

Locate and import a database driver that will translate requests you make
through functions in the database/sql package into requests the database
understands.

  1. In your browser, visit the SQLDrivers
    wiki page to identify a driver you can use.

    Use the list on the page to identify the driver you’ll use. For accessing
    MySQL in this tutorial, you’ll use
    Go-MySQL-Driver.

  2. Note the package name for the driver – here, github.com/go-sql-driver/mysql.

  3. Using your text editor, create a file in which to write your Go code and
    save the file as main.go in the data-access directory you created earlier.

  4. Into main.go, paste the following code to import the driver package.

    package main
    
    import "github.com/go-sql-driver/mysql"
    

    In this code, you:

    • Add your code to a main package so you can execute it independently.

    • Import the MySQL driver github.com/go-sql-driver/mysql.

With the driver imported, you’ll start writing code to access the database.

Get a database handle and connect

Now write some Go code that gives you database access with a database handle.

You’ll use a pointer to an sql.DB struct, which represents access to a
specific database.

Write the code

  1. Into main.go, beneath the import code you just added, paste the following
    Go code to create a database handle.

    var db *sql.DB
    
    func main() {
        // Capture connection properties.
        cfg := mysql.Config{
            User:   os.Getenv("DBUSER"),
            Passwd: os.Getenv("DBPASS"),
            Net:    "tcp",
            Addr:   "127.0.0.1:3306",
            DBName: "recordings",
        }
        // Get a database handle.
        var err error
        db, err = sql.Open("mysql", cfg.FormatDSN())
        if err != nil {
            log.Fatal(err)
        }
    
        pingErr := db.Ping()
        if pingErr != nil {
            log.Fatal(pingErr)
        }
        fmt.Println("Connected!")
    }
    

    In this code, you:

    • Declare a db variable of type *sql.DB.
      This is your database handle.

      Making db a global variable simplifies this example. In
      production, you’d avoid the global variable, such as by passing the
      variable to functions that need it or by wrapping it in a struct.

    • Use the MySQL driver’s Config
      – and the type’s FormatDSN
      -– to collect connection properties and format them into a DSN for a connection string.

      The Config struct makes for code that’s easier to read than a
      connection string would be.

    • Call sql.Open
      to initialize the db variable, passing the return value of
      FormatDSN.

    • Check for an error from sql.Open. It could fail if, for
      example, your database connection specifics weren’t well-formed.

      To simplify the code, you’re calling log.Fatal to end
      execution and print the error to the console. In production code, you’ll
      want to handle errors in a more graceful way.

    • Call DB.Ping to
      confirm that connecting to the database works. At run time,
      sql.Open might not immediately connect, depending on the
      driver. You’re using Ping here to confirm that the
      database/sql package can connect when it needs to.

    • Check for an error from Ping, in case the connection failed.

    • Print a message if Ping connects successfully.

  2. Near the top of the main.go file, just beneath the package declaration,
    import the packages you’ll need to support the code you’ve just written.

    The top of the file should now look like this:

    package main
    
    import (
        "database/sql"
        "fmt"
        "log"
        "os"
    
        "github.com/go-sql-driver/mysql"
    )
    
  3. Save main.go.

Run the code

  1. Begin tracking the MySQL driver module as a dependency.

    Use the go get
    to add the github.com/go-sql-driver/mysql module as a dependency for your
    own module. Use a dot argument to mean “get dependencies for code in the
    current directory.”

    $ go get .
    go get: added github.com/go-sql-driver/mysql v1.6.0
    

    Go downloaded this dependency because you added it to the import
    declaration in the previous step. For more about dependency tracking,
    see Adding a dependency.

  2. From the command prompt, set the DBUSER and DBPASS environment variables
    for use by the Go program.

    On Linux or Mac:

    $ export DBUSER=username
    $ export DBPASS=password
    

    On Windows:

    C:Usersyoudata-access> set DBUSER=username
    C:Usersyoudata-access> set DBPASS=password
    
  3. From the command line in the directory containing main.go, run the code by
    typing go run with a dot argument to mean “run the package in the
    current directory.”

    $ go run .
    Connected!
    

You can connect! Next, you’ll query for some data.

Query for multiple rows

In this section, you’ll use Go to execute an SQL query designed to return
multiple rows.

For SQL statements that might return multiple rows, you use the Query method
from the database/sql package, then loop through the rows it returns. (You’ll
learn how to query for a single row later, in the section
Query for a single row.)

Write the code

  1. Into main.go, immediately above func main, paste the following definition
    of an Album struct. You’ll use this to hold row data returned from the
    query.

    type Album struct {
        ID     int64
        Title  string
        Artist string
        Price  float32
    }
    
  2. Beneath func main, paste the following albumsByArtist function to query
    the database.

    // albumsByArtist queries for albums that have the specified artist name.
    func albumsByArtist(name string) ([]Album, error) {
        // An albums slice to hold data from returned rows.
        var albums []Album
    
        rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
        if err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        defer rows.Close()
        // Loop through rows, using Scan to assign column data to struct fields.
        for rows.Next() {
            var alb Album
            if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
                return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
            }
            albums = append(albums, alb)
        }
        if err := rows.Err(); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        return albums, nil
    }
    

    In this code, you:

    • Declare an albums slice of the Album type you defined. This will hold
      data from returned rows. Struct field names and types correspond to
      database column names and types.

    • Use DB.Query to
      execute a SELECT statement to query for albums with the
      specified artist name.

      Query’s first parameter is the SQL statement. After the
      parameter, you can pass zero or more parameters of any type. These provide
      a place for you to specify the values for parameters in your SQL statement.
      By separating the SQL statement from parameter values (rather than
      concatenating them with, say, fmt.Sprintf), you enable the
      database/sql package to send the values separate from the SQL
      text, removing any SQL injection risk.

    • Defer closing rows so that any resources it holds will be released when
      the function exits.

    • Loop through the returned rows, using
      Rows.Scan to
      assign each row’s column values to Album struct fields.

      Scan takes a list of pointers to Go values, where the column
      values will be written. Here, you pass pointers to fields in the
      alb variable, created using the & operator.
      Scan writes through the pointers to update the struct fields.

    • Inside the loop, check for an error from scanning column values into the
      struct fields.

    • Inside the loop, append the new alb to the albums slice.

    • After the loop, check for an error from the overall query, using
      rows.Err. Note that if the query itself fails, checking for an error
      here is the only way to find out that the results are incomplete.

  3. Update your main function to call albumsByArtist.

    To the end of func main, add the following code.

    albums, err := albumsByArtist("John Coltrane")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Albums found: %vn", albums)
    

    In the new code, you now:

    • Call the albumsByArtist function you added, assigning its return value to
      a new albums variable.

    • Print the result.

Run the code

From the command line in the directory containing main.go, run the code.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]

Next, you’ll query for a single row.

Query for a single row

In this section, you’ll use Go to query for a single row in the database.

For SQL statements you know will return at most a single row, you can use
QueryRow, which is simpler than using a Query loop.

Write the code

  1. Beneath albumsByArtist, paste the following albumByID function.

    // albumByID queries for the album with the specified ID.
    func albumByID(id int64) (Album, error) {
        // An album to hold data from the returned row.
        var alb Album
    
        row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
        if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            if err == sql.ErrNoRows {
                return alb, fmt.Errorf("albumsById %d: no such album", id)
            }
            return alb, fmt.Errorf("albumsById %d: %v", id, err)
        }
        return alb, nil
    }
    

    In this code, you:

    • Use DB.QueryRow
      to execute a SELECT statement to query for an album with the
      specified ID.

      It returns an sql.Row. To simplify the calling code
      (your code!), QueryRow doesn’t return an error. Instead,
      it arranges to return any query error (such as sql.ErrNoRows)
      from Rows.Scan later.

    • Use Row.Scan to copy
      column values into struct fields.

    • Check for an error from Scan.

      The special error sql.ErrNoRows indicates that the query returned no
      rows. Typically that error is worth replacing with more specific text,
      such as “no such album” here.

  2. Update main to call albumByID.

    To the end of func main, add the following code.

    // Hard-code ID 2 here to test the query.
    alb, err := albumByID(2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Album found: %vn", alb)
    

    In the new code, you now:

    • Call the albumByID function you added.

    • Print the album ID returned.

Run the code

From the command line in the directory containing main.go, run the code.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}

Next, you’ll add an album to the database.

Add data

In this section, you’ll use Go to execute an SQL INSERT statement to add a
new row to the database.

You’ve seen how to use Query and QueryRow with SQL statements that
return data. To execute SQL statements that don’t return data, you use Exec.

Write the code

  1. Beneath albumByID, paste the following addAlbum function to insert a new
    album in the database, then save the main.go.

    // addAlbum adds the specified album to the database,
    // returning the album ID of the new entry
    func addAlbum(alb Album) (int64, error) {
        result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
        if err != nil {
            return 0, fmt.Errorf("addAlbum: %v", err)
        }
        id, err := result.LastInsertId()
        if err != nil {
            return 0, fmt.Errorf("addAlbum: %v", err)
        }
        return id, nil
    }
    

    In this code, you:

    • Use DB.Exec to
      execute an INSERT statement.

      Like Query, Exec takes an SQL statement followed
      by parameter values for the SQL statement.

    • Check for an error from the attempt to INSERT.

    • Retrieve the ID of the inserted database row using
      Result.LastInsertId.

    • Check for an error from the attempt to retrieve the ID.

  2. Update main to call the new addAlbum function.

    To the end of func main, add the following code.

    albID, err := addAlbum(Album{
        Title:  "The Modern Sound of Betty Carter",
        Artist: "Betty Carter",
        Price:  49.99,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID of added album: %vn", albID)
    

    In the new code, you now:

    • Call addAlbum with a new album, assigning the ID of the album you’re
      adding to an albID variable.

Run the code

From the command line in the directory containing main.go, run the code.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
ID of added album: 5

Conclusion

Congratulations! You’ve just used Go to perform simple actions with a
relational database.

Suggested next topics:

  • Take a look at the data access guide, which includes more information
    about the subjects only touched on here.

  • If you’re new to Go, you’ll find useful best practices described in
    Effective Go and How to write Go code.

  • The Go Tour is a great step-by-step
    introduction to Go fundamentals.

Completed code

This section contains the code for the application you build with this tutorial.

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

var db *sql.DB

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

func main() {
    // Capture connection properties.
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // Get a database handle.
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")

    albums, err := albumsByArtist("John Coltrane")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Albums found: %vn", albums)

    // Hard-code ID 2 here to test the query.
    alb, err := albumByID(2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Album found: %vn", alb)

    albID, err := addAlbum(Album{
        Title:  "The Modern Sound of Betty Carter",
        Artist: "Betty Carter",
        Price:  49.99,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID of added album: %vn", albID)
}

// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
    // An albums slice to hold data from returned rows.
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // Loop through rows, using Scan to assign column data to struct fields.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
    // An album to hold data from the returned row.
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}

Понравилась статья? Поделить с друзьями:
  • Error python is missing or unusable
  • Error python interpreter is not selected please setup python interpreter first что делать
  • Error python interpreter is not selected please setup python interpreter first перевод
  • Error python interpreter is not selected please setup python interpreter first pycharm
  • Error pyspark does not support any application options