dev0_07_plpgsql_arrays
dev0_07_plpgsql_arrays
Arrays
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
2
Array types
Array
a set of numbered elements of the same type
one-dimensional, multidimensional
Initialization
usage without an explicit declaration (type_name[])
implicit declaration when creating a base type or a table (_type_name)
Usage
elements as scalar values
array slices
operations on arrays: comparison, inclusion, intersection, concatenation,
usage with ANY or ALL instead of a subquery, etc.
=> DO $$
DECLARE
a integer[2]; -- the size is ignored
BEGIN
a := ARRAY[10,20,30];
RAISE NOTICE '%', a;
-- by default, one-based indexing is used
RAISE NOTICE 'a[1] = %, a[2] = %, a[3] = %', a[1], a[2], a[3];
-- array slice
RAISE NOTICE 'Slice [2:3] = %', a[2:3];
-- assign values to the array slice
a[2:3] := ARRAY[222,333];
-- output the array
RAISE NOTICE '%', a;
END;
$$ LANGUAGE plpgsql;
NOTICE: {10,20,30}
NOTICE: a[1] = 10, a[2] = 20, a[3] = 30
NOTICE: Slice [2:3] = {20,30}
NOTICE: {10,222,333}
DO
A one-dimensional array can be constructed element by element: it will be expanded automatically if required. If you omit some of
the elements, they will receive NULL values.
=> DO $$
DECLARE
a integer[];
BEGIN
a[2] := 10;
a[3] := 20;
a[6] := 30;
RAISE NOTICE '%', a;
END;
$$ LANGUAGE plpgsql;
NOTICE: [2:6]={10,20,NULL,NULL,30}
DO
Since element numbering begins with a value other than one, the array itself is preceded by a range of element indexes.
CREATE TYPE
=> DO $$
DECLARE
c currency[]; -- composite type array
BEGIN
-- assign values to array elements
c[1].amount := 10; c[1].code := 'RUB';
c[2].amount := 50; c[2].code := 'KZT';
RAISE NOTICE '%', c;
END
$$ LANGUAGE plpgsql;
NOTICE: {"(10,RUB)","(50,KZT)"}
DO
=> DO $$
DECLARE
a integer[];
BEGIN
a := ARRAY( SELECT n FROM generate_series(1,3) n );
RAISE NOTICE '%', a;
END
$$ LANGUAGE plpgsql;
NOTICE: {1,2,3}
DO
You can also do the inverse operation: convert an array into a table:
unnest
--------
1
2
3
(3 rows)
Fun fact: the IN clause with a list of values is transformed into a search operation over the array:
QUERY PLAN
---------------------------------------------
Function Scan on generate_series g
Filter: (id = ANY ('{1,2,3}'::integer[]))
(2 rows)
Arrays and loops
To iterate through array elements, you can simply set up an integer FOR
loop using functions that return the minimum and the maximum index of the
array.
But there is also a specialized loop: FOREACH. In this case, a loop variable
iterates through the elements, not their indexes. That’s why the variable
must be of the same type as the array elements (as always, if the elements
are records, you can replace a single composite variable with several scalar
ones).
https://postgrespro.com/docs/postgresql/17/plpgsql-control-structures#PLP
GSQL-FOREACH-ARRAY
Arrays and loops
You can set up a loop that iterates through index values of array elements. The second parameter of the array_lower and
array_upper functions defines the array dimension (1 denotes a one-dimensional array).
=> DO $$
DECLARE
a integer[] := ARRAY[10,20,30];
BEGIN
FOR i IN array_lower(a,1)..array_upper(a,1) LOOP
RAISE NOTICE 'a[%] = %', i, a[i];
END LOOP;
END
$$ LANGUAGE plpgsql;
NOTICE: a[1] = 10
NOTICE: a[2] = 20
NOTICE: a[3] = 30
DO
If you do not need to know index values, it’s easier to iterate directly though the elements:
=> DO $$
DECLARE
a integer[] := ARRAY[10,20,30];
x integer;
BEGIN
FOREACH x IN ARRAY a LOOP
RAISE NOTICE '%', x;
END LOOP;
END
$$ LANGUAGE plpgsql;
NOTICE: 10
NOTICE: 20
NOTICE: 30
DO
Arrays and routines
Using arrays, you can create routines (functions and procedures) with a
variable number of arguments.
While parameters with default values have to be explicitly specified in
routine declaration, optional arguments can be passed with no limit: they are
provided as an array. Consequently, all of them must be of the same type
(or a compatible one, if anycompatible or anycompatiblearray is used).
The last parameter in routine declaration must be marked as VARIADIC; it
must be of an array type.
https://postgrespro.com/docs/postgresql/17/xfunc-sql#XFUNC-SQL-VARIAD
IC-FUNCTIONS
We have already mentioned polymorphic routines that can accept
arguments of various types. Routine declaration uses a special polymorphic
pseudotype, while the actual type is defined at run time based on the types
of the passed arguments.
There are special polymorphic types anyarray and anycompatiblearray (and
anynonarray and anycompatiblenonarrray for non-arrays).
These types can be used when passing a variable number of arguments via
a VARIADIC parameter.
https://postgrespro.com/docs/postgresql/17/xfunc-sql#XFUNC-SQL-POLYM
ORPHIC-FUNCTIONS
Arrays and routines
When discussing polymorphism and overloading as part of the “SQL. Functions and Procedures” lecture, we created the maximum
function to compare three numbers and find the greatest one. Now let’s generalize this function, so that it can be used with an
arbitrary number of arguments. For this purpose, we’ll declare a single VARIADIC parameter:
CREATE FUNCTION
maximum
---------
65
(1 row)
maximum
---------
87
(1 row)
maximum
---------
(1 row)
To complete this illustration, we can make this function polymorphic as well, so that it takes any data type (which supports
comparison operators, of course).
DROP FUNCTION
Polymorphic types anyarray and anyelement must match each other: anyarray = anyelement[];
The variable must be of the same type as the array element. But it cannot be declared as anyelement: it must have an actual
type. The %TYPE construct helps us out here.
CREATE FUNCTION
maximum
---------
65.3
(1 row)
maximum
---------
1500
(1 row)
Now our function is almost completely analogous to the greatest expression provided in SQL.
An array or a table?
1 1 separate tables:
1 A ...
1 ... 2 2 many to many relationship
2 B ...
2 ...
2 3
a universal solution
2 4
3 C ...
3 ... 3 1
3 3 4 D ...
Imagine that we are designing a database for writing a blog. The blog contains some posts, and we would like to tag them.
The traditional approach is to create a separate table for tags. For example:
CREATE TABLE
CREATE TABLE
Let’s connect posts and tags by a many-to-many relationship via an additional table:
CREATE TABLE
INSERT 0 2
INSERT 0 3
INSERT 0 4
message | name
-------------------------------------------------------------+----------------------
I set my password to “incorrect”. | my past and thoughts
I set my password to “incorrect”. | technology
Your future is whatever you make it, so make it a good one. | family
Your future is whatever you make it, so make it a good one. | my past and thoughts
(4 rows)
Or we can do it a bit differently to get an array of tags. We are going to use an aggregate function for this purpose:
-------------------------------------------------------------+----------------------------
---------
I set my password to “incorrect”. | {"my past and
thoughts",technology}
Your future is whatever you make it, so make it a good one. | {family,"my past and
thoughts"}
(2 rows)
message
-------------------------------------------------------------
I set my password to “incorrect”.
Your future is whatever you make it, so make it a good one.
(2 rows)
We may also need to find all the unique tags, and it is really easy:
name
----------------------
family
my past and thoughts
technology
(3 rows)
Now let’s try another approach to this task. Suppose the tags are stored as a text array right inside the table with posts.
DROP TABLE
DROP TABLE
ALTER TABLE
UPDATE 1
UPDATE 1
message | tags
-------------------------------------------------------------+----------------------------
---------
I set my password to “incorrect”. | {"my past and
thoughts",technology}
Your future is whatever you make it, so make it a good one. | {"my past and
thoughts",family}
(2 rows)
It is also easy to find all posts with the same tag (using the intersection operator &&).
This operation can be sped up using GIN index, and the query won’t require searching through the whole table of posts.
message
-------------------------------------------------------------
I set my password to “incorrect”.
Your future is whatever you make it, so make it a good one.
(2 rows)
But it is quite hard to get the list of all tags. It requires unnesting all the tag arrays into a big table, and the search for unique values
in this table is quite resource-intensive.
name
----------------------
family
technology
my past and thoughts
(3 rows)
In more complex scenarios (imagine that we would like to store the date of tag creation together with its name, or we need to use
check constraints), the traditional approach becomes more preferable.
Takeaways
11
Practice
12
1.
FUNCTION add_book(title text, authors integer[])
RETURNS integer
Task 1. The add_book function
CREATE FUNCTION