dev0_04_plpgsql_introduction
dev0_04_plpgsql_introduction
17
Copyright
© Postgres Professional, 2017–2025
Authors Egor Rogov, Pavel Luzanov, Ilya Bashtanov, Igor Gnatyuk
Translated by Liudmila Mantrova and Alexander Meleshko
Photo by Oleg Bartunov (Tukuche peak, Nepal)
Feedback
Please send your feedback, comments and suggestions to:
[email protected]
Disclaimer
In no event shall Postgres Professional company be liable for any damages
or loss, including loss of profits, that arise from direct or indirect, special or
incidental use of course materials. Postgres Professional company
specifically disclaims any warranties on course materials. Course materials
are provided “as is,” and Postgres Professional company has no obligations
to provide maintenance, support, updates, enhancements, or modifications.
Topics
PL/pgSQL history
Block structure and declaration of variables
Anonymous blocks
Routines in PL/pgSQL
Conditional operators and loops
Expression computing
2
PL/pgSQL history
Block label
Declaration of variables
the lifetime of a variable is limited to a block
the visibility scope can be overridden by a nested block, but a variable can
still be referenced by a block label
any SQL types and references to object types (%TYPE) are allowed
Operators
control structures
SQL operators, except for the service ones
nested blocks
Exception handling
You can use PL/pgSQL without creating routines. The PL/pgSQL code can
be written as an anonymous block and executed using the SQL’s DO
command.
This command can be used with various server languages, but if you do not
specify the language explicitly, it will be assumed that PL/pgSQL is used.
The code of anonymous blocks is not saved on the server. Anonymous
blocks do not take arguments or return any values (but there are ways to
circumvent that: for example, by using tables).
https://postgrespro.com/docs/postgresql/16/sql-do
Anonymous blocks
<<label>>
DECLARE
-- declaration of variables
BEGIN
-- operators
EXCEPTION
-- error handling
END label;
=> DO $$
BEGIN
-- there can be no operators
END
$$;
DO
=> DO $$
DECLARE
-- This is a one-line comment.
/* This is a multi-line comment.
Each declaration is ended by a semicolon ';'.
A semicolon is also placed after each operator.
*/
foo text;
bar text := 'World'; -- you can also use = or DEFAULT
BEGIN
foo := 'Hello'; -- this is an assignment operation
RAISE NOTICE '%, %!', foo, bar; -- message output
END
$$;
=> DO $$
DECLARE
foo integer NOT NULL := 0;
bar CONSTANT text := 42;
BEGIN
bar := bar + 1; -- error
END
$$;
Here is an example of nested blocks. A variable in the inner block overrides the one declared in the outer block, but you can refer to
any of them using labels:
=> DO $$
<<outer_block>>
DECLARE
foo text := 'Hello';
BEGIN
<<inner_block>>
DECLARE
foo text := 'World';
BEGIN
RAISE NOTICE '%, %!', outer_block.foo, inner_block.foo;
RAISE NOTICE 'An inner variable, without a label: %', foo;
END inner_block;
END outer_block
$$;
We have already learned about stored functions and procedures, using the
SQL language as an example. Most of the information related to creation
and management of routines applies to PL/pgSQL routines as well:
●
creating, modifying, and deleting routines,
●
location in the system catalog (pg_proc),
●
parameters,
●
return value and volatility categories (for functions),
●
overloading and polymorphism,
●
etc.
While SQL routines return a value produced by the last SQL operator,
PL/pgSQL routines either have to assign return values to INOUT or OUT
parameters, or use a special RETURN operator (which is available for
functions).
PL/pgSQL routines
Here is an example of a function that returns a value using the RETURN operator:
CREATE FUNCTION
Now let’s take a look at the same function with the OUT parameter. The return value is assigned to this parameter:
CREATE FUNCTION
Here is the same function with the INOUT parameter. This parameter is used for both providing input values and returning the
function value:
CREATE FUNCTION
IF
a regular conditional operator
CASE
similar to CASE in the SQL language, but does not return a value
IF condition THEN
-- operators
ELSIF condition THEN
-- operators
ELSE
-- operators
END IF;
The ELSIF section can be used several times, or there can be no such section at all.
There can be no ELSE section.
The operators corresponding to the first true condition will be executed.
If none of the conditions is true, the operators of the ELSE section are executed (if available).
Consider an example of a function that uses a conditional operator for decoding an ISBN-10 number. The function returns three
values:
CREATE FUNCTION
CASE
WHEN condition THEN
-- operators
ELSE
-- operators
END CASE;
There can be several WHEN sections.
There can be no ELSE section.
The operators corresponding to the first true condition will be executed.
If none of the conditions is true, ELSE operators are executed (it is an error to have no ELSE in this case).
Usage example:
=> DO $$
DECLARE
country text := (decode_isbn('1484268849')).country;
BEGIN
CASE
WHEN country IN ('0','1') THEN
RAISE NOTICE '% — English-speaking area', country;
WHEN country = '7' THEN
RAISE NOTICE '% — Russia', country;
WHEN country = '88' THEN
RAISE NOTICE '% — Italy', country;
ELSE
RAISE NOTICE '% — Other', country;
END CASE;
END
$$;
CASE expression
WHEN value, ... THEN
-- operators
ELSE
-- operators
END CASE;
If conditions are similar, this form of the CASE operator can turn out to be shorter:
=> DO $$
DECLARE
country text := (decode_isbn('8845210669')).country;
BEGIN
CASE country
WHEN '0', '1' THEN
RAISE NOTICE '% — English-speaking area', country;
WHEN '7' THEN
RAISE NOTICE '% — Russia', country;
WHEN '88' THEN
RAISE NOTICE '% — Italy', country;
ELSE
RAISE NOTICE '% — Other', country;
END CASE;
END
$$;
NOTICE: 88 — Italy
DO
Loops
11
LOOP
-- operators
END LOOP;
It can be extended by a header that defines the exit condition for the loop.
A FOR loop over a range is executed while the loop counter goes over the values from bottom to top. Each iteration increases the
counter by 1 (but the increment can be changed in the optional BY clause).
The variable used as a counter is declared implicitly and exists only within the LOOP — END LOOP block.
If REVERSE is specified, the counter value is reduced with each iteration, and the top and bottom of the loop have to be swapped:
CREATE FUNCTION
As you might remember, a STRICT function returns NULL right away if at least one of the input parameters is undefined. The
function body is not executed in this case.
WHILE condition
LOOP
-- operators
END LOOP;
Here is the same function that reverses a string using a WHILE loop:
CREATE FUNCTION
A LOOP without a header runs infinitely. To terminate it, use the EXIT operator.
The label is optional; if it is not specified, the innermost loop will be terminated.
The WHEN condition is also optional; if it is not specified, the loop is exited unconditionally.
CREATE FUNCTION
The function body is placed into an implicit block, with the function name used as the block label. So you can access
parameters using the “function_name.parameter” notation.
It is sometimes useful to apply the CONTINUE operator, which starts a new iteration of the loop:
=> DO $$
DECLARE
s integer := 0;
BEGIN
FOR i IN 1 .. 100
LOOP
s := s + i;
CONTINUE WHEN mod(i, 10) != 0;
RAISE NOTICE 'i = %, s = %', i, s;
END LOOP;
END
$$;
NOTICE: i = 10, s = 55
NOTICE: i = 20, s = 210
NOTICE: i = 30, s = 465
NOTICE: i = 40, s = 820
NOTICE: i = 50, s = 1275
NOTICE: i = 60, s = 1830
NOTICE: i = 70, s = 2485
NOTICE: i = 80, s = 3240
NOTICE: i = 90, s = 4095
NOTICE: i = 100, s = 5050
DO
Expression computing
13
Any PL/pgSQL expression is computed using the SQL engine. Thus, PL/pgSQL provides exactly the same features as SQL. For
example, since SQL allows using CASE, the same construct will also work in PL/pgSQL code (as an expression; it should not be
confused with the CASE ... END CASE operator, which is available only in PL/pgSQL):
=> DO $$
BEGIN
RAISE NOTICE '%', CASE 2+2 WHEN 4 THEN 'Everything is OK' END;
END
$$;
NOTICE: Everything is OK
DO
=> DO $$
BEGIN
RAISE NOTICE '%', (
SELECT code
FROM (VALUES (1, 'One'), (2, 'Two')) t(id, code)
WHERE id = 1
);
END
$$;
NOTICE: One
DO
Another PL/pgSQL expression computation example: how many string reverse functions did we have in total?
=> DO $$
DECLARE
s integer;
BEGIN
s := count(*) FROM pg_proc WHERE proname LIKE 'reverse%';
RAISE NOTICE 'Total "reverse" functions : %', s;
END
$$;
15
Practice
16
1. For example:
Travels into Several Remote Nations of the World. In Four Parts.
By Lemuel Gulliver, First a Surgeon, and then a Captain of Several
Ships →
→ Travels into Several Remote Nations of the W...
Here are some cases that are worth checking for:
●
The title length is less than 47 characters (should not change).
●
The title length is exactly 47 characters (should not change).
●
The title length is 48 characters (four characters have to be truncated
because three dots will be added).
It is recommended to implement and debug a separate function for
truncation, and then use it in book_name. It is useful for other reasons as
well:
●
It may come in handy somewhere else.
●
Each function will perform exactly one task.
2. For example:
Travels into Several Remote Nations of the World. In Four Parts.
By Lemuel Gulliver, First a Surgeon, and then a Captain of Several
Ships →
→ Travels into Several Remote Nations of the...
Will your implementation work properly if the title consists of a single long
word without spaces?
Task 1. Truncating book titles
Let’s create a more general function that accepts the following parameters: the string to truncate, the maximum length, and the
suffix to be used in case of truncation. It won’t complicate the code and will allow us to do without magic numbers.
CREATE FUNCTION
shorten
-------------------------------------------------
Travels into Several Remote Nations of the W...
(1 row)
shorten
------------------------------------
Travels into Several Remote Nat...
(1 row)
CREATE FUNCTION
)
RETURNS text
AS $$
DECLARE
suffix_len integer := length(suffix);
short text := suffix;
BEGIN
IF length(s) < max_len THEN
RETURN s;
END IF;
FOR pos in 1 .. least(max_len-suffix_len+1, length(s))
LOOP
IF substr(s,pos-1,1) != ' ' AND substr(s,pos,1) = ' ' THEN
short := left(s, pos-1) || suffix;
END IF;
END LOOP;
RETURN short;
END
$$ IMMUTABLE LANGUAGE plpgsql;
CREATE FUNCTION
shorten
-----------------------------------------------
Travels into Several Remote Nations of the...
(1 row)
shorten
--------------------------------
Travels into Several Remote...
(1 row)