09 Lesson 09: SQL / Procedures
This page is extracted from the official PDF into native Markdown. Slide text, code and speaker notes are kept in reading order; the original PDF and HTML remain the layout references. HTML source: dev1_09_sql_proc.html Archive: DEV1-handouts-16-en.zip PDF pages: 19
Page 1: SQL / Procedures
Copyright
© Postgres Professional, 2017–2025 Authors: Egor Rogov, Pavel Luzanov, Ilya Bashtanov, Igor Gnatyuk Translated by: Liudmila Mantrova, Alexander Meleshko, Elena Sharafutdinova Photo by: Oleg Bartunov (Phu monastery, Bhrikuti summit, Nepal)
Use of Course Materials
Non-commercial use of course materials (presentations, demonstrations) is allowed without restrictions. Commercial use is possible only with the written permission of Postgres Professional. It is prohibited to make changes to the course materials.
Feedback
Please send your feedback, comments and suggestions to: edu@postgrespro.ru
Disclaimer
Postgres Professional assumes no responsibility for any damages and losses, including loss of income, caused by direct or indirect, intentional or accidental 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.
Page 2: Topics
- Procedures and their Differences from Functions
- Input and Output Parameters
- Overloading and Polymorphism
Page 3: Routines
Functions
- called in the context of an expression
- cannot manage transactions
- return a result
- Procedures
- called using a CALL statement
- can manage transactions
- can return a result
Speaker notes
Procedures were first introduced in PostgreSQL 11. The main reason for their introduction was that functions cannot manage transactions. Functions are called in the context of some expression which is computed as part of an already started operator (such as SELECT) in an already started transaction. It is impossible to complete a transaction and then start a new one while the operator is being executed.
Procedures are always called by a special CALL statement. If this statement starts a new transaction (instead of being called from an already started one), then it is possible to use transaction management commands in the called procedure.
Unfortunately, procedures written in SQL cannot use COMMIT and ROLLBACK commands (although those with unquoted body may be able to in the future). Therefore, we won’t see an example of a procedure that manages transactions until we get to the PL/pgSQL. Query execution section.
Some say that the difference between functions and procedures is that a procedure does not return a result. But it is not true: procedures can also return a result, if required.
An umbrella term for both functions and procedures is routines. They share the common namespace.
https://postgrespro.com/docs/postgresql/16/sql-createprocedure
https://postgrespro.com/docs/postgresql/16/sql-call
Page 4: Procedures without Parameters
Let’s start with an example of a simple procedure with no parameters.
To call a procedure, you have to use a CALL statement:
Take a look at the result in the table:
Let’s define the procedure again, now with unquoted body:
Check if it works:
Try committing a transaction within the procedure:
Note Note that we get the invalid command error already at the procedure definition stage. Rename the table the procedure is working with:
Page 5: Page 5
The call below will not result in an error. In the procedure definition in the system catalog, the table is specified not by name but by ID, which was obtained at procedure creation.
Similar behavior can be achieved with a function which returns the output of its last operator. You can define the return type as void if the function does not return anything, or specify the actual result value type. Let’s give the table back its previous name and define the function:
In any case, a function is always called in the context of some expression:
Functions cannot manage transactions. But SQL procedures do not support it either (although procedures written in other languages do provide such support).
Procedures with Parameters
Let’s add an input parameter that defines the number of rows:
Just like functions, procedures allow passing arguments by position or by name:
Page 6: Page 6
Procedures can also have OUT and INOUT parameters that can be used to return a value.
Let’s try it out:
Page 7: Overloading
Several routines with the same name
- routine is uniquely identified by a signature:
- its name and input parameter types
- types of the return value and output parameters are ignored
- an appropriate routine is selected during execution based on the argument
- types
- CREATE OR REPLACE command
- for new combination of input parameter types,
- creates a new overloaded routine
- for existing combination of input parameter types,
- changes the corresponding routine, but not the type of the return value
Speaker notes
Overloading is the ability to use one and the same name for several routines (functions or procedures), which differ in types of IN and INOUT parameters.
In other words, a routine name and types of its input parameters form a routine signature. When calling a routine, PostgreSQL finds its version that corresponds to the passed arguments. If an appropriate routine cannot be determined unambiguously, a runtime error occurs.
A signature, however, does not include:
routine type (procedure or function),
OUT parameter types,
returned value type.
You have to take overloading into account when executing CREATE OR REPLACE (FUNCTION or PROCEDURE). If input parameter types differ from those used by already existing routines, a new overloaded routine will be created, otherwise a matching existing one will be replaced. Besides, when an existing routine is replaced with the CREATE OR REPLACE command, its type, OUT parameter types and return value type may not be changed, but other properties such as the language can be. In some cases, this means you must delete the routine and create it anew to replace it. However, doing so requires you to first delete all dependent objects, such as views, triggers, and other routines (DROP ROUTINE … CASCADE).
https://postgrespro.com/docs/postgresql/16/xfunc-overload
Page 8: Polymorphism
A routine that takes arguments of various types
- formal parameters use polymorphic pseudotypes
- (such as anyelement or anycompatible)
- the actual data type is selected during execution
- based on the type of the passed arguments
Speaker notes
Instead of having several overloaded routines for different types, it is sometimes more convenient to create a single routine that takes arguments of any (or almost any) type.
For this purpose, a special polymorphic pseudotype is used as the formal parameter type. For now, we will consider just two of them — anyelement and anycompatible — with more to follow in later sections.
A routine defined with polymorphic pseudotypes as input parameters may take any data type as input. The exact type to be used by the routine is selected at run time based on the type of the passed argument.
If a routine is defined with multiple polymorphic parameters of the anyelement type, all passed arguments will be implicitly converted to the type of the first parameter. On the other hand, if a routine is defined with multiple polymorphic parameters of the anycompatible type, all passed arguments will be converted to some common type.
If a function is declared with a polymorphic return value, it must have at least one polymorphic input parameter. The exact type of the return value is also defined by the actual type of the passed input argument. For SQL routines with unquoted body, there is no way to use polymorphic data types for arguments.
https://postgrespro.com/docs/postgresql/16/extend-type-system#EXTEND-T YPES-POLYMORPHIC
https://postgrespro.com/docs/postgresql/16/xfunc-sql#XFUNC-SQL-POLYM ORPHIC-FUNCTIONS
Page 9: Overloaded Routines
Overloading mechanism is the same for both functions and procedures. They have a common namespace. As an example, let’s create a function that compares two integer numbers and returns the largest value. (There is a similar SQL expression called greatest, but we’ll write our own function here.)
Let’s check:
Suppose we decided to create a similar function for three numbers. Thanks to overloading, we do not need to invent a new name:
Now we have two functions with the same name but a different number of parameters:
And both of them work:
The CREATE OR REPLACE command enables you to create a routine or replace an existing one without deleting it. Since a function with such a signature already exists, it will be replaced:
Let our function support not only integers but also real numbers. How can we implement it? We could define one more function as follows:
Page 10: Page 10
Now we have three functions with the same name:
Two of them have the same number of parameters that differ in types:
If a routine is overloaded multiple times, you can output information on specific overloads in \df by specifying parameter types:
Then we would have to define separate functions with exactly the same body for all other data types, and repeat it for three parameters.
Polymorphic Routines
We can use the polymorphic types anyelement and anycompatible. These are pseudotypes, and when a function is called and interpreted, they are substituted with actual argument types. Naturally, if a routine is defined with unquoted body, its code is parsed at creation, preventing the use of pseudotypes. Let’s delete all the three functions that we have created…
…and then create a new one:
This function should accept any data type (but will work only with those types for which the “greater than” operator is defined). Will it work?
Unfortunately not. In this case, string literals can be of the char, varchar, or text type; the exact type is unknown. But we can use explicit type casting:
Page 11: Page 11
Here is another example with a different type:
The type of the result value will always be the same as the parameter type. But we could go further and make polymorphic routines take not just the same types but compatible ones: those that can be implicitly converted into each other. This is where the polymorphic pseudotype anycompatible comes in. Let’s recreate our function:
Try the literals again:
It works! But if the types are neither the same nor compatible, we get an error:
In this example, such a requirement looks quite natural, but it may turn out to be inconvenient in some other cases. Now let’s create a function with three parameters, so that the third parameter is optional.
Page 12: Page 12
It works. And what about the following query?
A conflict occurs between two overloaded functions:
It’s impossible to understand whether we meant to run the function with two parameters, or simply omitted the third one. This conflict can be easily resolved: let’s delete the first function as it is no longer required.
Now everything works fine. Once we get to the PL/pgSQL. Arrays lecture, we will also learn how to define routines with an arbitrary number of parameters.
Page 13: Takeaways
You can create and use custom procedures
- Unlike functions, procedures are called using the CALL statement and can manage transactions
- Both procedures and functions support overloading and polymorphism
Page 14: Practice
- In the authors table, authors’ first, last and middle names must be unique, but this condition is not checked. Create a procedure that deletes possible duplicates. 2. To eliminate the need for such a procedure, create a constraint that will not allow entering duplicates in the future.
Speaker notes
- The feature for adding new authors won’t appear in the application until we get to the PL/pgSQL. Query execution section. For now, you can insert duplicates manually to check if your solution works.
Page 15: Task 1. Eliminating Duplicates
To test the solution, add another Pushkin:
There are several ways to eliminate duplicates. One example is:
Task 2. Using Constraints
We cannot define a suitable constraint because a middle name can be NULL. NULL values are considered to be different, so the constraint
will still allow you to add another Jonathan Swift without a middle name. The problem can be solved by creating a unique index:
Page 16: Page 16
Let’s check the result:
Page 17: Practice+
- Is it possible to create the following objects with the same name that belong to the same schema: 1) a procedure with one input parameter? 2) a function with one input parameter of the same type that returns some value? 3) a procedure with one input and one output parameter? Try and see. 2. A table stores real numbers (for example, measurements of some sort). Create a procedure that performs data normalization by multiplying all numbers by a certain factor, so that all values fit the interval between −1 and 1. The procedure must return the selected normalization factor.
Speaker notes
- Take the maximum absolute value from the table as the normalization factor.
Page 18: 1. Procedure and function overloading
This will not work, because the signature of a routine includes only the name and types of the input parameters (the return value is ignored), and the procedures and functions share a common namespace.
In some messages, as in this one, “function” is used instead of the word “procedure”, because they are similar in many ways.
This also doesn’t work, since there is already a procedure with the same signature, and it is also forbidden to change the output parameters (and their presence) for an existing routine. The routine must be deleted before recreating it.
2. Data Normalization
Table with test data:
Just a single SQL statement is enough: