Skip to main content
A stored procedure is a database object consisting of or statements that can be issued with a single statement. This allows complex logic to be executed repeatedly within the database, which can improve performance and mitigate security risks. Both stored procedures and user-defined functions are types of routines. However, they differ in the following ways:
  • Functions return a value, and procedures do not return a value.
  • Procedures must be invoked using a statement. Functions can be invoked in nearly any context, such as SELECT, FROM, and WHERE clauses, expressions, and expressions.
  • Functions have settings, and procedures do not.

Structure

A stored procedure consists of a name, optional parameters, language, and procedure body.
  • Each parameter can be a supported , , or the PL/pgSQL REFCURSOR type, when .
  • CockroachDB supports the IN (default), OUT, and INOUT modes for parameters. For an example, see .
  • LANGUAGE specifies the language of the function body. CockroachDB supports the languages and .
  • The procedure body:
    • Can be enclosed in single or dollar ($$) quotes. Dollar quotes are easier to use than single quotes, which require that you escape other single quotes that are within the procedure body.
    • Must conform to a if written in PL/pgSQL.
For details, see .

DDL and DCL statements

Supported statements and requirements

Stored procedures support the following and statements: The support described here applies only to stored procedures. DDL and DCL statements remain unsupported in and blocks. Other DDL statements, including , , , and , remain unsupported in stored procedures. Enabling late binding does not enable additional DDL statements. The value of the cluster setting determines how a PL/pgSQL procedure is bound when the procedure is created or replaced. Changing this setting does not change the binding of existing procedures. To enable late binding for an existing procedure, enable the setting and issue . Late-bound procedures can create and reference temporary tables, which remain scoped to the session.

Transaction and security behavior

Procedures that contain DDL or DCL must be invoked by a standalone , which runs as an implicit transaction. In other words, they must be run outside an explicit transaction. This restriction does not apply to a procedure that contains only . Run the standalone CALL at SERIALIZABLE isolation. If an implicit transaction uses or REPEATABLE READ isolation, CockroachDB attempts to restart the transaction at SERIALIZABLE isolation and emits the notice setting transaction isolation level to SERIALIZABLE due to schema change. CockroachDB rejects the CALL when it cannot restart the transaction, including in the following cases:
  • After another statement in the transaction has executed.
  • When the DDL or DCL is reached only through a nested CALL.
  • When an active portal from the extended query protocol prevents the restart.
DDL and DCL changes in a procedure are transactional. If the CALL fails or the transaction is rolled back, the changes are rolled back. A PL/pgSQL block with an EXCEPTION clause uses a : when an exception is caught, changes made inside that block are rolled back, while changes completed before the block are preserved. DDL and DCL follow the procedure’s security mode. In a procedure, privilege checks and current_user resolve to the procedure owner, and objects created by the procedure are owned by that user. For details, refer to .

Statement statistics

SQL statements executed within stored procedures are tracked in the SQL statistics subsystem and will appear in the page and the page in the DB Console. This allows you to monitor the performance and execution statistics of individual statements within your procedures. When the stored procedure is invoked as part of a transaction, the statements executed within the procedure will also appear in the in the Statement Fingerprints table. For each , CockroachDB exports the sql.routine.{type}.started.count metric, which counts statements started within routines, and sql.routine.{type}.count, which counts statements successfully executed within routines. Metrics for internal queries append .internal to the metric name. The {type} placeholder in the metric name is one of the following:
  • create_table
  • create_temp_table
  • drop_table
  • create_schema
  • drop_schema
  • create_role
  • drop_role
  • grant
  • revoke
  • alter_default_privileges
cannot be collected for statements executed inside stored procedures. You can request statement diagnostics for the top-level invocation of the procedure, and the resulting trace includes spans for each statement executed. However, there is no way to target statements executed inside the procedure with a statement diagnostics request. For details, refer to Known limitations.

Examples

Setup

To follow along, run to start a temporary, in-memory cluster with the sample dataset preloaded:
For more examples of stored procedure creation, see .

Create a stored procedure using PL/pgSQL

The following stored procedure removes a specified number of earliest rides in vehicle_location_histories. It uses the PL/pgSQL syntax to iterate through the rows, [RAISE] to return notice and error messages, and REFCURSOR to define a cursor that fetches the next rows to be affected by the procedure.
Open a :
Call the stored procedure, specifying 5 rows to delete and a rides_left cursor name:
Use the cursor to fetch the 3 earliest remaining rows in vehicle_location_histories:
If the procedure is called again, these rows will be the first 3 to be deleted.

Example details

The example works as follows: defines a stored procedure called delete_earliest_histories with an INT and a REFCURSOR parameter.
LANGUAGE specifies as the language for the stored procedure.
DECLARE specifies the that are used in the procedure body.
BEGIN and END in the procedure body.
The following statement if vehicle_location_histories has fewer rows than the number specified with num_deletions. If the exception is raised within an open transaction, the transaction will abort.
The following loop deletes rows iteratively from vehicle_location_histories, stopping when the number of loops reaches the num_deletions value. The DELETE ... RETURNING ... INTO statement assigns column values from each deleted row into separate variables. For more information about assigning variables, see . Finally, the statement reports these values for each deleted row.
The OPEN statement for all remaining rows in vehicle_location_histories, sorted by timestamp. After calling the procedure in an open transaction, the cursor can be used to fetch rows from the table.
For more details on this example, see the .

Execute DDL in a PL/pgSQL procedure

The following example creates a self-initializing application event log. Each time the procedure is called, it creates the audit_events table if needed and records an event.
In a late-bound PL/pgSQL procedure, each top-level statement is planned immediately before it runs. Therefore, the can reference the table created by the preceding statement. By contrast, SQL language procedures use early binding, so referenced objects must exist when the procedure is created.

Alter a stored procedure

The following statement renames the to delete_histories:

Known limitations

Stored procedures have the following limitations:
  • Pausable portals are not supported with CALL statements for stored procedures.
  • COMMIT and ROLLBACK statements are not supported within nested procedures.
  • Routines cannot be invoked with named arguments, e.g., SELECT foo(a => 1, b => 2); or SELECT foo(b := 1, a := 2);.
  • Temporary tables cannot be created within or blocks. Within a PL/pgSQL stored procedure body, creating a temporary table requires the to be enabled.
  • Routines cannot be created with unnamed INOUT parameters. For example, CREATE PROCEDURE p(INOUT INT) AS $$ BEGIN NULL; END; $$ LANGUAGE PLpgSQL;.
  • Routines cannot be created if they return fewer columns than declared. For example, CREATE FUNCTION f(OUT sum INT, INOUT a INT, INOUT b INT) LANGUAGE SQL AS $$ SELECT (a + b, b); $$;.
  • Routines cannot be created with an OUT parameter of type RECORD.
  • Stored procedures do not support arbitrary DDL. For the supported statements and requirements, refer to DDL and DCL statements.
  • Within a PL/pgSQL IF, loop, nested block, or EXCEPTION handler, a statement cannot reference an object created earlier in the same control-flow construct. Move the reference after the construct, or move the DDL and the reference to the top level of the procedure body.
  • A stored procedure that contains DDL or DCL statements cannot be called within an explicit transaction.
  • Polymorphic types cannot be cast to other types (e.g., TEXT) within routine parameters.
  • Routine parameters and return types cannot be declared using the ANYENUM polymorphic type, which is able to match any type.
  • cannot be collected for statements executed inside UDFs or stored procedures. You can request statement diagnostics for the top-level invocation of the function or procedure, and the resulting trace includes spans for each statement executed. However, there is no way to target statements executed inside the function or procedure with a statement diagnostics request.
  • Statements within routines do not currently respect hint injections. The workaround is to modify the inline hints directly in the body by replacing the routine.
Also refer to the .

See also