7.7. Writing the Body Code
This section takes a closer look at the procedural SQL language constructs and statements that are available for coding the body of a stored procedure, functions, trigger, and PSQL blocks.
7.7.1. Assignment Statements
Assigns a value to a variable
Syntax
varname = <value_expr>;
| Argument | Description |
|---|---|
varname | Name of a parameter or local variable |
value_expr | An expression, constant or variable whose value resolves to the same data type as varname |
PSQL uses the equal symbol (
) as its assignment operator.
The assignment statement assigns a SQL expression value on the right to the variable on the left of the operator.
The expression can be any valid SQL expression: it may contain literals, internal variable names, arithmetic, logical and string operations, calls to internal functions, stored functions or external functions (UDFs).=
7.7.1.1. Example using assignment statements
CREATE PROCEDURE MYPROC (a INTEGER,b INTEGER,name VARCHAR (30))RETURNS (c INTEGER,str VARCHAR(100))ASBEGIN-- assigning a constantc = 0;str = '';SUSPEND;-- assigning expression valuesc = a + b;str = name || CAST(b AS VARCHAR(10));SUSPEND;-- assigning expression value built by a queryc = (SELECT 1 FROM rdb$database);-- assigning a value from a context variablestr = CURRENT_USER;SUSPEND;END
7.7.2. Management Statements in PSQL
Management statement are allowed in PSQL modules (triggers, procedures, functions and PSQL blocks), which is especially helpful for applications that need management statements to be executed at the start of a session, specifically in ON CONNECT triggers.
The management statements permitted in PSQL are:
7.7.2.1. Example of Management Statements in PSQL
create or alter trigger on_connect on connectasbeginset bind of decfloat to double precision;set time zone 'America/Sao_Paulo';end
Although useful as a workaround, using ON CONNECT triggers to configure bind and time zone is usually not the right approach.
Alternatives are handling this through DefaultTimeZone in firebird.conf and DataTypeCompatibility in firebird.conf or databases.conf, or isc_dpb_session_time_zone or isc_dpb_set_bind in the DPB.
See alsoManagement Statements
7.7.3. DECLARE VARIABLE
Declares a local variable
Syntax
DECLARE [VARIABLE] varname
<domain_or_non_array_type> [NOT NULL] [COLLATE collation]
[{DEFAULT | = } <initvalue>];
<domain_or_non_array_type> ::=
!! See Scalar Data Types Syntax !!
<initvalue> ::= <literal> | <context_var>
DECLARE VARIABLE Statement Parameters| Argument | Description |
|---|---|
varname | Name of the local variable |
collation | Collation |
initvalue | Initial value for this variable |
literal | Literal of a type compatible with the type of the local variable |
context_var | Any context variable whose type is compatible with the type of the local variable |
The statement DECLARE [VARIABLE] is used for declaring a local variable.
One DECLARE [VARIABLE] statement is required for each local variable.
Any number of DECLARE [VARIABLE] statements can be included and in any order.
The name of a local variable must be unique among the names of local variables and input and output parameters declared for the module.
A special case of DECLARE [VARIABLE] — declaring cursors — is covered separately in Section 7.7.4, “DECLARE .. CURSOR”
7.7.3.1. Data Type for Variables
A local variable can be of any SQL type other than an array.
A domain name can be specified as the type; the variable will inherit all of its attributes.
If the
TYPE OF domainclause is used instead, the variable will inherit only the domain’s data type, and, if applicable, its character set and collation attributes. Any default value or constraints such asNOT NULLorCHECKconstraints are not inherited.If the
TYPE OF COLUMN relation.columnoption is used toborrow
from a column in a table or view, the variable will inherit only the column’s data type, and, if applicable, its character set and collation attributes. Any other attributes are ignored.
7.7.3.2. NOT NULL Constraint
For local variables, you can specify the NOT NULL constraint, disallowing NULL values for the variable.
If a domain has been specified as the data type and the domain already has the NOT NULL constraint, the declaration is unnecessary.
For other forms, including use of a domain that is nullable, the NOT NULL constraint can be included if needed.
7.7.3.3. CHARACTER SET and COLLATE clauses
Unless specified, the character set and collation of a string variable will be the database defaults.
A CHARACTER SET clause can be specified to handle string data that needs a different character set.
A valid collation (COLLATE clause) can also be included, with or without the character set clause.
7.7.3.4. Initializing a Variable
Local variables are NULL when execution of the module begins.
They can be explicitly initialized so that a starting or default value is available when they are first referenced.
The initial value can be specified in two ways, DEFAULT <initvalue> and = <initvalue>.
The value can be any type-compatible literal or context variable, including NULL.
Be sure to use the DEFAULT clause for any variable that has a NOT NULL constraint and does not otherwise have a default value available (i.e. inherited from a domain).
7.7.3.5. Examples of various ways to declare local variables
CREATE OR ALTER PROCEDURE SOME_PROCAS-- Declaring a variable of the INT typeDECLARE I INT;-- Declaring a variable of the INT type that does not allow NULLDECLARE VARIABLE J INT NOT NULL;-- Declaring a variable of the INT type with the default value of 0DECLARE VARIABLE K INT DEFAULT 0;-- Declaring a variable of the INT type with the default value of 1DECLARE VARIABLE L INT = 1;-- Declaring a variable based on the COUNTRYNAME domainDECLARE FARM_COUNTRY COUNTRYNAME;-- Declaring a variable of the type equal to the COUNTRYNAME domainDECLARE FROM_COUNTRY TYPE OF COUNTRYNAME;-- Declaring a variable with the type of the CAPITAL column in the COUNTRY tableDECLARE CAPITAL TYPE OF COLUMN COUNTRY.CAPITAL;BEGIN/* PSQL statements */END
See alsoData Types and Subtypes, Custom Data Types — Domains, CREATE DOMAIN
7.7.4. DECLARE .. CURSOR
Declares a named cursor
Syntax
DECLARE [VARIABLE] cursor_name
[[NO] SCROLL] CURSOR
FOR (<select>);
DECLARE … CURSOR Statement Parameters| Argument | Description |
|---|---|
cursor_name | Cursor name |
select |
|
The DECLARE … CURSOR … FOR statement binds a named cursor to the result set obtained by the SELECT statement specified in the FOR clause.
In the body code, the cursor can be opened, used to iterate row-by-row through the result set, and closed.
While the cursor is open, the code can perform positioned updates and deletes using the WHERE CURRENT OF in the UPDATE or DELETE statement.
Syntactically, the DECLARE … CURSOR statement is a special case of Section 7.7.3, “DECLARE VARIABLE”.
7.7.4.1. Forward-Only and Scrollable Cursors
The cursor can be forward-only (unidirectional) or scrollable.
The optional clause SCROLL makes the cursor scrollable, the NO SCROLL clause, forward-only.
By default, cursors are forward-only.
Forward-only cursors can — as the name implies — only move forward in the dataset.
Forward-only cursors only support the FETCH [NEXT FROM] statement, other fetch options raise an error.
Scrollable cursors allow you to move not only forward in the dataset, but also back, as well as N positions relative to the current position.
Scrollable cursors are materialized as a temporary dataset, as such, they consume additional memory or disk space, so use them only when you really need them.
7.7.4.2. Cursor Idiosyncrasies
The optional
FOR UPDATEclause can be included in theSELECTstatement, but its absence does not prevent successful execution of a positioned update or deleteCare should be taken to ensure that the names of declared cursors do not conflict with any names used subsequently in statements for
AS CURSORclausesIf the cursor is needed only to walk the result set, it is nearly always easier and less error-prone to use a
FOR SELECTstatement with theAS CURSORclause. Declared cursors must be explicitly opened, used to fetch data, and closed. The context variableROW_COUNThas to be checked after each fetch and, if its value is zero, the loop has to be terminated. AFOR SELECTstatement does this automatically.Nevertheless, declared cursors provide a high level of control over sequential events and allow several cursors to be managed in parallel.
The
SELECTstatement may contain parameters. For instance:SELECT NAME || :SFX FROM NAMES WHERE NUMBER = :NUMEach parameter has to have been declared beforehand as a PSQL variable, or as input or output parameters. When the cursor is opened, the parameter is assigned the current value of the variable.
If the value of the PSQL variable used in the SELECT statement of the cursor changes during the execution of the loop, then its new value may — but not always — be used when selecting the next rows.
It is better to avoid such situations.
If you really need this behaviour, then you should thoroughly test your code and make sure you understand how changes to the variable affect the query results.
Note particularly that the behaviour may depend on the query plan, specifically on the indexes being used. Currently, there are no strict rules for this behaviour, and this may change in future versions of Firebird.
7.7.4.3. Examples Using Named Cursors
Declaring a named cursor in a trigger.
CREATE OR ALTER TRIGGER TBU_STOCKBEFORE UPDATE ON STOCKASDECLARE C_COUNTRY CURSOR FOR (SELECTCOUNTRY,CAPITALFROM COUNTRY);BEGIN/* PSQL statements */ENDDeclaring a scrollable cursor
EXECUTE BLOCKRETURNS (N INT,RNAME CHAR(63))AS- Declaring a scrollable cursorDECLARE C SCROLL CURSOR FOR (SELECTROW_NUMBER() OVER (ORDER BY RDB$RELATION_NAME) AS N,RDB$RELATION_NAMEFROM RDB$RELATIONSORDER BY RDB$RELATION_NAME);BEGIN/ * PSQL statements * /ENDA collection of scripts for creating views with a PSQL block using named cursors.
EXECUTE BLOCKRETURNS (SCRIPT BLOB SUB_TYPE TEXT)ASDECLARE VARIABLE FIELDS VARCHAR(8191);DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME;DECLARE VARIABLE RELATION RDB$RELATION_NAME;DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE;DECLARE VARIABLE CUR_R CURSOR FOR (SELECTRDB$RELATION_NAME,RDB$VIEW_SOURCEFROMRDB$RELATIONSWHERERDB$VIEW_SOURCE IS NOT NULL);-- Declaring a named cursor where-- a local variable is usedDECLARE CUR_F CURSOR FOR (SELECTRDB$FIELD_NAMEFROMRDB$RELATION_FIELDSWHERE-- the variable must be declared earlierRDB$RELATION_NAME = :RELATION);BEGINOPEN CUR_R;WHILE (1 = 1) DOBEGINFETCH CUR_RINTO :RELATION, :SOURCE;IF (ROW_COUNT = 0) THENLEAVE;FIELDS = NULL;-- The CUR_F cursor will use the value-- of the RELATION variable initiated aboveOPEN CUR_F;WHILE (1 = 1) DOBEGINFETCH CUR_FINTO :FIELD_NAME;IF (ROW_COUNT = 0) THENLEAVE;IF (FIELDS IS NULL) THENFIELDS = TRIM(FIELD_NAME);ELSEFIELDS = FIELDS || ', ' || TRIM(FIELD_NAME);ENDCLOSE CUR_F;SCRIPT = 'CREATE VIEW ' || RELATION;IF (FIELDS IS NOT NULL) THENSCRIPT = SCRIPT || ' (' || FIELDS || ')';SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13);SCRIPT = SCRIPT || SOURCE;SUSPEND;ENDCLOSE CUR_R;END
See alsoSection 7.7.18, “OPEN”, Section 7.7.19, “FETCH”, Section 7.7.20, “CLOSE”
7.7.5. DECLARE FUNCTION
Declares a sub-function
Syntax
<subfunc-forward> ::= <subfunc-header>;
<subfunc-def> ::= <subfunc-header> <psql-module-body>
<subfunc-header> ::=
DECLARE FUNCTION subfuncname [ ( [ <in_params> ] ) ]
RETURNS <domain_or_non_array_type> [COLLATE collation]
[DETERMINISTIC]
<in_params> ::=
!! See
CREATE FUNCTION Syntax !!
<domain_or_non_array_type> ::=
!! See Scalar Data Types Syntax !!
<psql-module-body> ::=
!! See Syntax of Module Body !!
DECLARE FUNCTION Statement Parameters| Argument | Description |
|---|---|
subfuncname | Sub-function name |
collation | Collation name |
The DECLARE FUNCTION statement declares a sub-function.
A sub-function is only visible to the PSQL module that defined the sub-function.
A sub-function can use variables, but not cursors, from its parent module. It can access other routines from its parent modules, including recursive calls to itself.
Sub-functions have a number of restrictions:
A sub-function cannot be nested in another subroutine. Subroutines are only supported in top-level PSQL modules (stored procedures, stored functions, triggers and PSQL blocks). This restriction is not enforced by the syntax, but attempts to create nested sub-functions will raise an error
feature is not supported
with detail messagenested sub function
.Currently, a sub-function has no direct access to use cursors from its parent module.
A sub-function can be forward declared to resolve mutual dependencies between subroutines, and must be followed by its actual definition. When a sub-function is forward declared and has parameters with default values, the default values should only be specified in the forward declaration, and should not be repeated in subfunc_def.
Declaring a sub-function with the same name as a stored function will hide that stored function from your module. It will not be possible to call that stored function.
Contrary to DECLARE [VARIABLE], a DECLARE FUNCTION is not terminated by a semicolon.
The END of its main BEGIN … END block is considered its terminator.
7.7.5.1. Examples of Sub-Functions
Sub-function within a stored function
CREATE OR ALTER FUNCTION FUNC1 (n1 INTEGER, n2 INTEGER)RETURNS INTEGERAS- SubfunctionDECLARE FUNCTION SUBFUNC (n1 INTEGER, n2 INTEGER)RETURNS INTEGERASBEGINRETURN n1 + n2;ENDBEGINRETURN SUBFUNC (n1, n2);ENDRecursive function call
execute block returns (i integer, o integer)as-- Recursive function without forward declaration.declare function fibonacci(n integer) returns integerasbeginif (n = 0 or n = 1) thenreturn n;elsereturn fibonacci(n - 1) + fibonacci(n - 2);endbegini = 0;while (i < 10)dobegino = fibonacci(i);suspend;i = i + 1;endend
7.7.6. DECLARE PROCEDURE
Declares a sub-procedure
Syntax
<subproc-forward> ::= <subproc-header>;
<subproc-def> ::= <subproc-header> <psql-module-body>
<subproc-header> ::=
DECLARE subprocname [ ( [ <in_params> ] ) ]
[RETURNS (<out_params>)]
<in_params> ::=
!! See
CREATE PROCEDURE Syntax !!
<domain_or_non_array_type> ::=
!! See Scalar Data Types Syntax !!
<psql-module-body> ::=
!! See Syntax of Module Body !!
DECLARE PROCEDURE Statement Parameters| Argument | Description |
|---|---|
subprocname | Sub-procedure name |
collation | Collation name |
The DECLARE PROCEDURE statement declares a sub-procedure.
A sub-procedure is only visible to the PSQL module that defined the sub-procedure.
A sub-procedure can use variables, but not cursors, from its parent module. It can access other routines from its parent modules.
Sub-procedures have a number of restrictions:
A sub-procedure cannot be nested in another subroutine. Subroutines are only supported in top-level PSQL modules (stored procedures, stored functions, triggers and PSQL blocks). This restriction is not enforced by the syntax, but attempts to create nested sub-procedures will raise an error
feature is not supported
with detail messagenested sub procedure
.Currently, the sub-procedure has no direct access to use cursors from its parent module.
A sub-procedure can be forward declared to resolve mutual dependencies between subroutines, and must be followed by its actual definition. When a sub-procedure is forward declared and has parameters with default values, the default values should only be specified in the forward declaration, and should not be repeated in subproc_def.
Declaring a sub-procedure with the same name as a stored procedure, table or view will hide that stored procedure, table or view from your module. It will not be possible to call that stored procedure, table or view.
Contrary to DECLARE [VARIABLE], a DECLARE PROCEDURE is not terminated by a semicolon.
The END of its main BEGIN … END block is considered its terminator.
7.7.6.1. Examples of Sub-Procedures
Subroutines in
EXECUTE BLOCKEXECUTE BLOCKRETURNS (name VARCHAR(63))AS-- Sub-procedure returning a list of tablesDECLARE PROCEDURE get_tablesRETURNS (table_name VARCHAR(63))ASBEGINFOR SELECT RDB$RELATION_NAMEFROM RDB$RELATIONSWHERE RDB$VIEW_BLR IS NULLINTO table_nameDO SUSPEND;END-- Sub-procedure returning a list of viewsDECLARE PROCEDURE get_viewsRETURNS (view_name VARCHAR(63))ASBEGINFOR SELECT RDB$RELATION_NAMEFROM RDB$RELATIONSWHERE RDB$VIEW_BLR IS NOT NULLINTO view_nameDO SUSPEND;ENDBEGINFOR SELECT table_nameFROM get_tablesUNION ALLSELECT view_nameFROM get_viewsINTO nameDO SUSPEND;ENDWith forward declaration and parameter with default value
execute block returns (o integer)as-- Forward declaration of P1.declare procedure p1(i integer = 1) returns (o integer);-- Forward declaration of P2.declare procedure p2(i integer) returns (o integer);-- Implementation of P1 should not re-declare parameter default value.declare procedure p1(i integer) returns (o integer)asbeginexecute procedure p2(i) returning_values o;enddeclare procedure p2(i integer) returns (o integer)asbegino = i;endbeginexecute procedure p1 returning_values o;suspend;end
7.7.7. BEGIN … END
Delimits a block of statements
Syntax
<block> ::=
BEGIN
[<compound_statement> ...]
[<when_do> ...]
END
<compound_statement> ::= {<block> | <statement>}
<when_do> ::=
!! See WHEN ... DO !!
The BEGIN … END construct is a two-part statement that wraps a block of statements that are executed as one unit of code.
Each block starts with the keyword BEGIN and ends with the keyword END.
Blocks can be nested a maximum depth of 512 nested blocks.
A block can be empty, allowing them to act as stubs, without the need to write dummy statements.
For error handling, you can add one or more Section 7.8.4, “WHEN … DO” statements immediately before END.
Other statements are not allowed after WHEN … DO.
The BEGIN … END itself should not be followed by a statement terminator (semicolon).
However, when defining or altering a PSQL module in the isql utility, that application requires that the last END statement be followed by its own terminator character, that was previously switched — using SET TERM — to a string other than a semicolon.
That terminator is not part of the PSQL syntax.
The final, or outermost, END statement in a trigger terminates the trigger.
What the final END statement does in a stored procedure depends on the type of procedure:
In a selectable procedure, the final
ENDstatement returns control to the caller, returning SQLCODE 100, indicating that there are no more rows to retrieveIn an executable procedure, the final
ENDstatement returns control to the caller, along with the current values of any output parameters defined.
7.7.7.1. BEGIN … END Examples
A sample procedure from the employee.fdb database, showing simple usage of BEGIN … END blocks:
SET TERM ^;
CREATE OR ALTER PROCEDURE DEPT_BUDGET (
DNO CHAR(3))
RETURNS (
TOT DECIMAL(12,2))
AS
DECLARE VARIABLE SUMB DECIMAL(12,2);
DECLARE VARIABLE RDNO CHAR(3);
DECLARE VARIABLE CNT INTEGER;
BEGIN
TOT = 0;
SELECT BUDGET
FROM DEPARTMENT
WHERE DEPT_NO = :DNO
INTO :TOT;
SELECT COUNT(BUDGET)
FROM DEPARTMENT
WHERE HEAD_DEPT = :DNO
INTO :CNT;
IF (CNT = 0) THEN
SUSPEND;
FOR SELECT DEPT_NO
FROM DEPARTMENT
WHERE HEAD_DEPT = :DNO
INTO :RDNO
DO
BEGIN
EXECUTE PROCEDURE DEPT_BUDGET(:RDNO)
RETURNING_VALUES :SUMB;
TOT = TOT + SUMB;
END
SUSPEND;
END^
SET TERM ;^
See alsoSection 7.7.13, “EXIT”, SET TERM, Section 7.8.4, “WHEN … DO”
7.7.8. IF … THEN … ELSE
Conditional branching
Syntax
IF (<condition>)
THEN <compound_statement>
[ELSE <compound_statement>]
IF … THEN … ELSE Parameters| Argument | Description |
|---|---|
condition | A logical condition returning |
compound_statement | A single statement, or statements wrapped in |
The conditional branch statement IF … THEN is used to branch the execution process in a PSQL module.
The condition is always enclosed in parentheses.
If the condition returns the value TRUE, execution branches to the statement or the block of statements after the keyword THEN.
If an ELSE is present, and the condition returns FALSE or UNKNOWN, execution branches to the statement or the block of statements after it.
7.7.8.1. IF Examples
An example using the
IFstatement. Assume that the variablesFIRST,LINE2andLASTwere declared earlier....IF (FIRST IS NOT NULL) THENLINE2 = FIRST || ' ' || LAST;ELSELINE2 = LAST;...Given
IF … THEN … ELSEis a statement, it is possible to chain them together. Assume that theINT_VALUEandSTRING_VALUEvariables were declared earlier.IF (INT_VALUE = 1) THENSTRING_VALUE = 'one';ELSE IF (INT_VALUE = 2) THENSTRING_VALUE = 'two';ELSE IF (INT_VALUE = 3) THENSTRING_VALUE = 'three';ELSESTRING_VALUE = 'too much';This specific example can be replaced with a simple
CASEor theDECODEfunction.
See alsoSection 7.7.9, “WHILE … DO”, CASE
7.7.9. WHILE … DO
Looping construct
Syntax
[label:]
WHILE (<condition>) DO
<compound_statement>
WHILE … DO Parameters| Argument | Description |
|---|---|
label | Optional label for |
condition | A logical condition returning |
compound_statement | A single statement, or statements wrapped in |
A WHILE statement implements the looping construct in PSQL.
The statement or the block of statements will be executed as long as the condition returns TRUE.
Loops can be nested to any depth.
7.7.9.1. WHILE … DO Examples
A procedure calculating the sum of numbers from 1 to I shows how the looping construct is used.
CREATE PROCEDURE SUM_INT (I INTEGER)RETURNS (S INTEGER)ASBEGINs = 0;WHILE (i > 0) DOBEGINs = s + i;i = i - 1;ENDEND
Executing the procedure in isql:
EXECUTE PROCEDURE SUM_INT(4);the result is:
S==========10
See alsoSection 7.7.8, “IF … THEN … ELSE”, Section 7.7.10, “BREAK”, Section 7.7.11, “LEAVE”, Section 7.7.12, “CONTINUE”, Section 7.7.13, “EXIT”, Section 7.7.16, “FOR SELECT”, Section 7.7.17, “FOR EXECUTE STATEMENT”
7.7.10. BREAK
Exits a loop
Syntax
[label:]
<loop_stmt>
BEGIN
...
BREAK;
...
END
<loop_stmt> ::=
FOR <select_stmt> INTO <var_list> DO
| FOR EXECUTE STATEMENT ... INTO <var_list> DO
| WHILE (<condition>)} DO
BREAK Statement Parameters| Argument | Description |
|---|---|
label | Label |
select_stmt |
|
condition | A logical condition returning TRUE, FALSE or UNKNOWN |
The BREAK statement immediately terminates the inner loop of a WHILE or FOR looping statement.
Code continues to be executed from the first statement after the terminated loop block.
BREAK is similar to LEAVE, except it doesn’t support a label.
See alsoSection 7.7.11, “LEAVE”
7.7.11. LEAVE
Exits a loop
Syntax
[label:]
<loop_stmt>
BEGIN
...
LEAVE [label];
...
END
<loop_stmt> ::=
FOR <select_stmt> INTO <var_list> DO
| FOR EXECUTE STATEMENT ... INTO <var_list> DO
| WHILE (<condition>)} DO
LEAVE Statement Parameters| Argument | Description |
|---|---|
label | Label |
select_stmt |
|
condition | A logical condition returning |
The LEAVE statement immediately terminates the inner loop of a WHILE or FOR looping statement.
Using the optional label parameter, LEAVE can also exit an outer loop, that is, the loop labelled with label.
Code continues to be executed from the first statement after the terminated loop block.
7.7.11.1. LEAVE Examples
Leaving a loop if an error occurs on an insert into the
NUMBERStable. The code continues to be executed from the lineC = 0....WHILE (B < 10) DOBEGININSERT INTO NUMBERS(B)VALUES (:B);B = B + 1;WHEN ANY DOBEGINEXECUTE PROCEDURE LOG_ERROR (CURRENT_TIMESTAMP,'ERROR IN B LOOP');LEAVE;ENDENDC = 0;...An example using labels in the
LEAVEstatement.LEAVE LOOPAterminates the outer loop andLEAVE LOOPBterminates the inner loop. Note that the plainLEAVEstatement would be enough to terminate the inner loop....STMT1 = 'SELECT NAME FROM FARMS';LOOPA:FOR EXECUTE STATEMENT :STMT1INTO :FARM DOBEGINSTMT2 = 'SELECT NAME ' || 'FROM ANIMALS WHERE FARM = ''';LOOPB:FOR EXECUTE STATEMENT :STMT2 || :FARM || ''''INTO :ANIMAL DOBEGINIF (ANIMAL = 'FLUFFY') THENLEAVE LOOPB;ELSE IF (ANIMAL = FARM) THENLEAVE LOOPA;SUSPEND;ENDEND...
See alsoSection 7.7.10, “BREAK”, Section 7.7.12, “CONTINUE”, Section 7.7.13, “EXIT”
7.7.12. CONTINUE
Continues with the next iteration of a loop
Syntax
[label:]
<loop_stmt>
BEGIN
...
CONTINUE [label];
...
END
<loop_stmt> ::=
FOR <select_stmt> INTO <var_list> DO
| FOR EXECUTE STATEMENT ... INTO <var_list> DO
| WHILE (<condition>)} DO
CONTINUE Statement Parameters| Argument | Description |
|---|---|
label | Label |
select_stmt |
|
condition | A logical condition returning |
The CONTINUE statement skips the remainder of the current block of a loop and starts the next iteration of the current WHILE or FOR loop.
Using the optional label parameter, CONTINUE can also start the next iteration of an outer loop, that is, the loop labelled with label.
7.7.12.1. CONTINUE Examples
Using the CONTINUE statement
FOR SELECT A, D
FROM ATABLE INTO achar, ddate
DO
BEGIN
IF (ddate < current_date - 30) THEN
CONTINUE;
/* do stuff */
END
See alsoSection 7.7.10, “BREAK”, Section 7.7.11, “LEAVE”, Section 7.7.13, “EXIT”
7.7.13. EXIT
Terminates execution of a module
Syntax
EXIT;
The EXIT statement causes execution of the current PSQL module to jump to the final END statement from any point in the code, thus terminating the program.
Calling EXIT in a function will result in the function returning NULL.
7.7.13.1. EXIT Examples
Using the EXIT statement in a selectable procedure
CREATE PROCEDURE GEN_100
RETURNS (I INTEGER)
AS
BEGIN
I = 1;
WHILE (1=1) DO
BEGIN
SUSPEND;
IF (I=100) THEN
EXIT;
I = I + 1;
END
END
See alsoSection 7.7.10, “BREAK”, Section 7.7.11, “LEAVE”, Section 7.7.12, “CONTINUE”, Section 7.7.14, “SUSPEND”
7.7.14. SUSPEND
Passes output to the buffer and suspends execution while waiting for caller to fetch it
Syntax
SUSPEND;
The SUSPEND statement is used in selectable stored procedures to pass the values of output parameters to a buffer and suspend execution.
Execution remains suspended until the calling application fetches the contents of the buffer.
Execution resumes from the statement directly after the SUSPEND statement.
In practice, this is likely to be a new iteration of a looping process.
The
SUSPENDstatement can only occur in stored procedures or sub-proceduresThe presence of the
SUSPENDkeyword defines a stored procedure as a selectable procedureApplications using interfaces that wrap the API perform the fetches from selectable procedures transparently.
If a selectable procedure is executed using
EXECUTE PROCEDURE, it behaves as an executable procedure. When aSUSPENDstatement is executed in such a stored procedure, it is the same as executing theEXITstatement, resulting in immediate termination of the procedure.SUSPENDbreaks
the atomicity of the block in which it is located. If an error occurs in a selectable procedure, statements executed after the finalSUSPENDstatement will be rolled back. Statements that executed before the finalSUSPENDstatement will not be rolled back unless the transaction is rolled back.
7.7.14.1. SUSPEND Examples
Using the SUSPEND statement in a selectable procedure
CREATE PROCEDURE GEN_100
RETURNS (I INTEGER)
AS
BEGIN
I = 1;
WHILE (1=1) DO
BEGIN
SUSPEND;
IF (I=100) THEN
EXIT;
I = I + 1;
END
END
See alsoSection 7.7.13, “EXIT”
7.7.15. EXECUTE STATEMENT
Executes dynamically created SQL statements
Syntax
<execute_statement> ::= EXECUTE STATEMENT <argument>
[<option> ...]
[INTO <variables>];
<argument> ::= <paramless_stmt>
| (<paramless_stmt>)
| (<stmt_with_params>) (<param_values>)
<param_values> ::= <named_values> | <positional_values>
<named_values> ::= <named_value> [, <named_value> ...]
<named_value> ::= [EXCESS] paramname := <value_expr>
<positional_values> ::= <value_expr> [, <value_expr> ...]
<option> ::=
WITH {AUTONOMOUS | COMMON} TRANSACTION
| WITH CALLER PRIVILEGES
| AS USER user
| PASSWORD password
| ROLE role
| ON EXTERNAL [DATA SOURCE] <connection_string>
<connection_string> ::=
!! See <filespec> in the
CREATE DATABASE syntax !!
<variables> ::= [:]varname [, [:]varname ...]
EXECUTE STATEMENT Statement Parameters| Argument | Description |
|---|---|
paramless_stmt | Literal string or variable containing a non-parameterized SQL query |
stmt_with_params | Literal string or variable containing a parameterized SQL query |
paramname | SQL query parameter name |
value_expr | SQL expression resolving to a value |
user | Username.
It can be a string, |
password | Password. It can be a string or a string variable |
role | Role.
It can be a string, |
connection_string | Connection string. It can be a string literal or a string variable |
varname | Variable |
The statement EXECUTE STATEMENT takes a string parameter and executes it as if it were a DSQL statement.
If the statement returns data, it can be passed to local variables by way of an INTO clause.
EXECUTE STATEMENT can only produce a single row of data.
Statements producing multiple rows of data must be executed with Section 7.7.17, “FOR EXECUTE STATEMENT”.
7.7.15.1. Parameterized Statements
You can use parameters — either named or positional — in the DSQL statement string. Each parameter must be assigned a value.
7.7.15.1.1. Special Rules for Parameterized Statements
Named and positional parameters cannot be mixed in one query
Each parameter must be used in the statement text.
To relax this rule, named parameters can be prefixed with the keyword
EXCESSto indicate that the parameter may be absent from the statement text. This option is useful for dynamically generated statements that conditionally include or exclude certain parameters.If the statement has parameters, they must be enclosed in parentheses when
EXECUTE STATEMENTis called, regardless of whether they come directly as strings, as variable names or as expressionsEach named parameter must be prefixed by a colon (
) in the statement string itself, but not when the parameter is assigned a value:Positional parameters must be assigned their values in the same order as they appear in the query text
The assignment operator for parameters is the special operator
, similar to the assignment operator in Pascal:=Each named parameter can be used in the statement more than once, but its value must be assigned only once
With positional parameters, the number of assigned values must match the number of parameter placeholders (question marks) in the statement exactly
A named parameter in the statement text can only be a regular identifier (it cannot be a quoted identifier)
7.7.15.1.2. Examples of EXECUTE STATEMENT with parameters
With named parameters:
...DECLARE license_num VARCHAR(15);DECLARE connect_string VARCHAR (100);DECLARE stmt VARCHAR (100) ='SELECT license ''FROM cars ''WHERE driver = :driver AND location = :loc';BEGIN-- ...EXECUTE STATEMENT (stmt)(driver := current_driver,loc := current_location)ON EXTERNAL connect_stringINTO license_num;The same code with positional parameters:
DECLARE license_num VARCHAR (15);DECLARE connect_string VARCHAR (100);DECLARE stmt VARCHAR (100) ='SELECT license ''FROM cars ''WHERE driver = ? AND location = ?';BEGIN-- ...EXECUTE STATEMENT (stmt)(current_driver, current_location)ON EXTERNAL connect_stringINTO license_num;Use of
EXCESSto allow named parameters to be unused (note: this is aFOR EXECUTE STATEMENT):
CREATE PROCEDURE P_EXCESS (A_ID INT, A_TRAN INT = NULL, A_CONN INT = NULL)RETURNS (ID INT, TRAN INT, CONN INT)ASDECLARE S VARCHAR(255) = 'SELECT * FROM TTT WHERE ID = :ID';DECLARE W VARCHAR(255) = '';BEGINIF (A_TRAN IS NOT NULL)THEN W = W || ' AND TRAN = :a';IF (A_CONN IS NOT NULL)THEN W = W || ' AND CONN = :b';IF (W <> '')THEN S = S || W;-- could raise error if TRAN or CONN is null-- FOR EXECUTE STATEMENT (:S) (a := :A_TRAN, b := A_CONN, id := A_ID)-- OK in all casesFOR EXECUTE STATEMENT (:S) (EXCESS a := :A_TRAN, EXCESS b := A_CONN, id := A_ID)INTO :ID, :TRAN, :CONNDO SUSPEND;END
7.7.15.2. WITH {AUTONOMOUS | COMMON} TRANSACTION
By default, the executed SQL statement runs within the current transaction.
Using WITH AUTONOMOUS TRANSACTION causes a separate transaction to be started.
This separate transaction will be committed when the statement was executed without errors and rolled back otherwise.
See Section 7.7.21, “IN AUTONOMOUS TRANSACTION” for more details on autonomous transactions.
The clause WITH COMMON TRANSACTION uses the current transaction whenever possible;
this is the default behaviour.
If the statement must run in a separate connection, an already started transaction within that connection is used, if available.
Otherwise, a new transaction is started with the same parameters as the current transaction.
Any new transactions started under the
regime are committed or rolled back with the current transaction.COMMON
7.7.15.3. WITH CALLER PRIVILEGES
By default, the SQL statement is executed with the privileges of the current user.
Specifying WITH CALLER PRIVILEGES combines the privileges of the calling procedure or trigger with those of the user, as if the statement were executed directly by the routine.
WITH CALLER PRIVILEGES has no effect if the ON EXTERNAL clause is also present.
7.7.15.4. ON EXTERNAL [DATA SOURCE]
With ON EXTERNAL [DATA SOURCE], the SQL statement is executed in a separate connection to the same or another database, possibly even on another server.
If connection_string is NULL or
(empty string), the entire ''ON EXTERNAL [DATA SOURCE] clause is considered absent, and the statement is executed against the current database.
7.7.15.4.1. Connection Pooling
External connections made by statements
WITH COMMON TRANSACTION(the default) will remain open until the current transaction ends. They can be reused by subsequent calls toEXECUTE STATEMENT, but only if connection_string is identical, including caseExternal connections made by statements
WITH AUTONOMOUS TRANSACTIONare closed as soon as the statement has been executedStatements using
WITH AUTONOMOUS TRANSACTIONcan and will re-use connections that were opened earlier by statementsWITH COMMON TRANSACTION. If this happens, the reused connection will be left open after the statement has been executed. (It must be, because it has at least one active transaction!)
7.7.15.4.2. Transaction Pooling
If
WITH COMMON TRANSACTIONis in effect, transactions will be reused as much as possible. They will be committed or rolled back together with the current transactionIf
WITH AUTONOMOUS TRANSACTIONis specified, a fresh transaction will always be started for the statement. This transaction will be committed or rolled back immediately after the statement’s execution
7.7.15.4.3. Exception Handling
When ON EXTERNAL is used, the extra connection is always made via a so-called external provider, even if the connection is to the current database.
One of the consequences is that exceptions cannot be caught in the usual way.
Every exception caused by the statement is wrapped in either an eds_connection or an eds_statement error.
To catch them in your PSQL code, you have to use WHEN GDSCODE eds_connection, WHEN GDSCODE eds_statement or WHEN ANY.
Without ON EXTERNAL, exceptions are caught in the usual way, even if an extra connection is made to the current database.
7.7.15.4.4. Miscellaneous Notes
The character set used for the external connection is the same as that for the current connection
Two-phase commits are not supported
7.7.15.5. AS USER, PASSWORD and ROLE
The optional AS USER, PASSWORD and ROLE clauses allow specification of which user will execute the SQL statement and with which role.
The method of user login, and whether a separate connection is opened, depends on the presence and values of the ON EXTERNAL [DATA SOURCE], AS USER, PASSWORD and ROLE clauses:
If
ON EXTERNALis present, a new connection is always opened, and:If at least one of
AS USER,PASSWORDandROLEis present, native authentication is attempted with the given parameter values (locally or remotely, depending on connection_string). No defaults are used for missing parametersIf all three are absent, and connection_string contains no hostname, then the new connection is established on the local server with the same user and role as the current connection. The term 'local' means
on the same machine as the server
here. This is not necessarily the location of the clientIf all three are absent, and connection_string contains a hostname, then trusted authentication is attempted on the remote host (again, 'remote' from the perspective of the server). If this succeeds, the remote operating system will provide the username (usually the operating system account under which the Firebird process runs)
If
ON EXTERNALis absent:If at least one of
AS USER,PASSWORDandROLEis present, a new connection to the current database is opened with the supplied parameter values. No defaults are used for missing parametersIf all three are absent, the statement is executed within the current connection
If a parameter value is NULL or
(empty string), the entire parameter is considered absent.
Additionally, ''AS USER is considered absent if its value is equal to CURRENT_USER, and ROLE if it is the same as CURRENT_ROLE.
7.7.15.6. Caveats with EXECUTE STATEMENT
There is no way to validate the syntax of the enclosed statement
There are no dependency checks to discover whether tables or columns have been dropped
Execution is considerably slower than when the same statements are executed directly as PSQL code
Return values are strictly checked for data type to avoid unpredictable type-casting exceptions. For example, the string
'1234'would convert to an integer, 1234, but'abc'would give a conversion error
All in all, this feature is meant to be used cautiously, and you should always take the caveats into account. If you can achieve the same result with PSQL and/or DSQL, it will almost always be preferable.
7.7.16. FOR SELECT
Loops row-by-row through a query result set
Syntax
[label:]
FOR <select_stmt> [AS CURSOR cursor_name]
DO <compound_statement>
FOR SELECT Statement Parameters| Argument | Description |
|---|---|
label | Optional label for |
select_stmt |
|
cursor_name | Cursor name. It must be unique among cursor names in the PSQL module (stored procedure, stored function, trigger or PSQL block) |
compound_statement | A single statement, or statements wrapped in |
The FOR SELECT statement
retrieves each row sequentially from the result set, and executes the statement or block of statements for each row. In each iteration of the loop, the field values of the current row are copied into pre-declared variables.
Including the
AS CURSORclause enables positioned deletes and updates to be performed — see notes belowcan embed other
FOR SELECTstatementscan contain named parameters that must be previously declared in the
DECLARE VARIABLEstatement or exist as input or output parameters of the procedurerequires an
INTOclause at the end of theSELECT … FROM …specification ifAS CURSORis absent In each iteration of the loop, the field values of the current row are copied to the list of variables specified in theINTOclause. The loop repeats until all rows are retrieved, after which it terminatescan be terminated before all rows are retrieved by using a
BREAK,LEAVEorEXITstatement
7.7.16.1. The Undeclared Cursor
The optional AS CURSOR clause surfaces the result set of the FOR SELECT structure as an undeclared, named cursor that can be operated on using the WHERE CURRENT OF clause inside the statement or block following the DO command, to delete or update the current row before execution moves to the next row.
In addition, it is possible to use the cursor name as a record variable (similar to OLD and NEW in triggers), allowing access to the columns of the result set (i.e. cursor_name.columnname).
When accessing a cursor variable in a DML statement, the colon prefix can be added before the cursor name (i.e.
:cursor_name.columnname) for disambiguation, similar to variables.The cursor variable can be referenced without colon prefix, but in that case, depending on the scope of the contexts in the statement, the name may resolve in the statement context instead of to the cursor (e.g. you select from a table with the same name as the cursor).
Cursor variables are read-only
In a
FOR SELECTstatement without anAS CURSORclause, you must use theINTOclause. If anAS CURSORclause is specified, theINTOclause is allowed, but optional; you can access the fields through the cursor instead.Reading from a cursor variable returns the current field values. This means that an
UPDATEstatement (with aWHERE CURRENT OFclause) will update not only the table, but also the fields in the cursor variable for subsequent reads. Executing aDELETEstatement (with aWHERE CURRENT OFclause) will set all fields in the cursor variable toNULLfor subsequent reads
Other points to take into account regarding undeclared cursors:
The
OPEN,FETCHandCLOSEstatements cannot be applied to a cursor surfaced by theAS CURSORclauseThe cursor_name argument associated with an
AS CURSORclause must not clash with any names created byDECLARE VARIABLEorDECLARE CURSORstatements at the top of the module body, nor with any other cursors surfaced by anAS CURSORclauseThe optional
FOR UPDATEclause in theSELECTstatement is not required for a positioned update
7.7.16.2. Examples using FOR SELECT
A simple loop through query results:
CREATE PROCEDURE SHOWNUMSRETURNS (AA INTEGER,BB INTEGER,SM INTEGER,DF INTEGER)ASBEGINFOR SELECT DISTINCT A, BFROM NUMBERSORDER BY A, BINTO AA, BBDOBEGINSM = AA + BB;DF = AA - BB;SUSPEND;ENDENDNested
FOR SELECTloop:CREATE PROCEDURE RELFIELDSRETURNS (RELATION CHAR(32),POS INTEGER,FIELD CHAR(32))ASBEGINFOR SELECT RDB$RELATION_NAMEFROM RDB$RELATIONSORDER BY 1INTO :RELATIONDOBEGINFOR SELECTRDB$FIELD_POSITION + 1,RDB$FIELD_NAMEFROM RDB$RELATION_FIELDSWHERERDB$RELATION_NAME = :RELATIONORDER BY RDB$FIELD_POSITIONINTO :POS, :FIELDDOBEGINIF (POS = 2) THENRELATION = ' "';SUSPEND;ENDENDEND☞TipInstead of nesting statements, this is generally better solved by using a single statements with a join.
Using the
AS CURSORclause to surface a cursor for the positioned delete of a record:CREATE PROCEDURE DELTOWN (TOWNTODELETE VARCHAR(24))RETURNS (TOWN VARCHAR(24),POP INTEGER)ASBEGINFOR SELECT TOWN, POPFROM TOWNSINTO :TOWN, :POP AS CURSOR TCURDOBEGINIF (:TOWN = :TOWNTODELETE) THEN-- Positional deleteDELETE FROM TOWNSWHERE CURRENT OF TCUR;ELSESUSPEND;ENDENDUsing an implicitly declared cursor as a cursor variable
EXECUTE BLOCKRETURNS (o CHAR(63))ASBEGINFOR SELECT rdb$relation_name AS nameFROM rdb$relations AS CURSOR cDOBEGINo = c.name;SUSPEND;ENDENDDisambiguating cursor variables within queries
EXECUTE BLOCKRETURNS (o1 CHAR(63), o2 CHAR(63))ASBEGINFOR SELECT rdb$relation_nameFROM rdb$relationsWHERErdb$relation_name = 'RDB$RELATIONS' AS CURSOR cDOBEGINFOR SELECT-- with a prefix resolves to the cursor:c.rdb$relation_name x1,-- no prefix as an alias for the rdb$relations tablec.rdb$relation_name x2FROM rdb$relations cWHERErdb$relation_name = 'RDB$DATABASE' AS CURSOR dDOBEGINo1 = d.x1;o2 = d.x2;SUSPEND;ENDENDEND
See alsoSection 7.7.4, “DECLARE .. CURSOR”, Section 7.7.10, “BREAK”, Section 7.7.11, “LEAVE”, Section 7.7.12, “CONTINUE”, Section 7.7.13, “EXIT”, SELECT, UPDATE, DELETE
7.7.17. FOR EXECUTE STATEMENT
Executes dynamically created SQL statements and loops over its result set
Syntax
[label:]
FOR <execute_statement> DO <compound_statement>
FOR EXECUTE STATEMENT Statement Parameters| Argument | Description |
|---|---|
label | Optional label for |
execute_stmt | An |
compound_statement | A single statement, or statements wrapped in |
The statement FOR EXECUTE STATEMENT is used, in a manner analogous to FOR SELECT, to loop through the result set of a dynamically executed query that returns multiple rows.
7.7.17.1. FOR EXECUTE STATEMENT Examples
Executing a dynamically constructed SELECT query that returns a data set
CREATE PROCEDURE DynamicSampleThree (
Q_FIELD_NAME VARCHAR(100),
Q_TABLE_NAME VARCHAR(100)
) RETURNS(
LINE VARCHAR(32000)
)
AS
DECLARE VARIABLE P_ONE_LINE VARCHAR(100);
BEGIN
LINE = '';
FOR
EXECUTE STATEMENT
'SELECT T1.' || :Q_FIELD_NAME ||
' FROM ' || :Q_TABLE_NAME || ' T1 '
INTO :P_ONE_LINE
DO
IF (:P_ONE_LINE IS NOT NULL) THEN
LINE = :LINE || :P_ONE_LINE || ' ';
SUSPEND;
END
See alsoSection 7.7.15, “EXECUTE STATEMENT”, Section 7.7.10, “BREAK”, Section 7.7.11, “LEAVE”, Section 7.7.12, “CONTINUE”
7.7.18. OPEN
Opens a declared cursor
Syntax
OPEN cursor_name;
OPEN Statement Parameter| Argument | Description |
|---|---|
cursor_name | Cursor name.
A cursor with this name must be previously declared with a |
An OPEN statement opens a previously declared cursor, executes its declared SELECT statement, and makes the first record of the result data set ready to fetch.
OPEN can be applied only to cursors previously declared in a Section 7.7.4, “DECLARE .. CURSOR” statement.
If the SELECT statement of the cursor has parameters, they must be declared as local variables, or input or output parameters before the cursor is declared.
When the cursor is opened, the parameter is assigned the current value of the variable.
7.7.18.1. OPEN Examples
Using the
OPENstatement:SET TERM ^;CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMESRETURNS (RNAME CHAR(63))ASDECLARE C CURSOR FOR (SELECT RDB$RELATION_NAMEFROM RDB$RELATIONS);BEGINOPEN C;WHILE (1 = 1) DOBEGINFETCH C INTO :RNAME;IF (ROW_COUNT = 0) THENLEAVE;SUSPEND;ENDCLOSE C;END^SET TERM ;^A collection of scripts for creating views using a PSQL block with named cursors:
EXECUTE BLOCKRETURNS (SCRIPT BLOB SUB_TYPE TEXT)ASDECLARE VARIABLE FIELDS VARCHAR(8191);DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME;DECLARE VARIABLE RELATION RDB$RELATION_NAME;DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE;-- named cursorDECLARE VARIABLE CUR_R CURSOR FOR (SELECTRDB$RELATION_NAME,RDB$VIEW_SOURCEFROMRDB$RELATIONSWHERERDB$VIEW_SOURCE IS NOT NULL);-- named cursor with local variableDECLARE CUR_F CURSOR FOR (SELECTRDB$FIELD_NAMEFROMRDB$RELATION_FIELDSWHERE-- Important! The variable has to be declared earlierRDB$RELATION_NAME = :RELATION);BEGINOPEN CUR_R;WHILE (1 = 1) DOBEGINFETCH CUR_RINTO :RELATION, :SOURCE;IF (ROW_COUNT = 0) THENLEAVE;FIELDS = NULL;-- The CUR_F cursor will use-- variable value of RELATION initialized aboveOPEN CUR_F;WHILE (1 = 1) DOBEGINFETCH CUR_FINTO :FIELD_NAME;IF (ROW_COUNT = 0) THENLEAVE;IF (FIELDS IS NULL) THENFIELDS = TRIM(FIELD_NAME);ELSEFIELDS = FIELDS || ', ' || TRIM(FIELD_NAME);ENDCLOSE CUR_F;SCRIPT = 'CREATE VIEW ' || RELATION;IF (FIELDS IS NOT NULL) THENSCRIPT = SCRIPT || ' (' || FIELDS || ')';SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13);SCRIPT = SCRIPT || SOURCE;SUSPEND;ENDCLOSE CUR_R;END
See alsoSection 7.7.4, “DECLARE .. CURSOR”, Section 7.7.19, “FETCH”, Section 7.7.20, “CLOSE”
7.7.19. FETCH
Fetches a record from a cursor
Syntax
FETCH [<fetch_scroll> FROM] cursor_name
[INTO [:]varname [, [:]varname ...]];
<fetch_scroll> ::=
NEXT | PRIOR | FIRST | LAST
| RELATIVE n | ABSOLUTE n
FETCH Statement Parameters| Argument | Description |
|---|---|
cursor_name | Cursor name.
A cursor with this name must be previously declared with a |
varname | Variable name |
n | Integer expression for the number of rows |
The FETCH statement fetches the next row from the result set of the cursor and assigns the column values to PSQL variables.
The FETCH statement can be used only with a cursor declared with the Section 7.7.4, “DECLARE .. CURSOR” statement.
Using the optional fetch_scroll part of the FETCH statement, you can specify in which direction and how many rows to advance the cursor position.
The NEXT fetch option can be used for scrollable and forward-only cursors.
Other fetch options are only supported for scrollable cursors.
NEXTmoves the cursor one row forward; this is the default
PRIORmoves the cursor one record back
FIRSTmoves the cursor to the first record.
LASTmoves the cursor to the last record
RELATIVE nmoves the cursor n rows from the current position; positive numbers move forward, negative numbers move backwards; using zero (
0) will not move the cursor, andROW_COUNTwill be set to zero as no new row was fetched.ABSOLUTE nmoves the cursor to the specified row; n is an integer expression, where
1indicates the first row. For negative values, the absolute position is taken from the end of the result set, so-1indicates the last row,-2the second to last row, etc. A value of zero (0) will position before the first row.
The optional INTO clause gets data from the current row of the cursor and loads them into PSQL variables.
If a fetch moves beyond the bounds of the result set, the variables will be set to NULL.
It is also possible to use the cursor name as a variable of a record type (similar to OLD and NEW in triggers), allowing access to the columns of the result set (i.e. cursor_name.columnname).
When accessing a cursor variable in a DML statement, the colon prefix can be added before the cursor name (i.e.
:cursor_name.columnname) for disambiguation, similar to variables.The cursor variable can be referenced without colon prefix, but in that case, depending on the scope of the contexts in the statement, the name may resolve in the statement context instead of to the cursor (e.g. you select from a table with the same name as the cursor).
Cursor variables are read-only
In a
FOR SELECTstatement without anAS CURSORclause, you must use theINTOclause. If anAS CURSORclause is specified, theINTOclause is allowed, but optional; you can access the fields through the cursor instead.Reading from a cursor variable returns the current field values. This means that an
UPDATEstatement (with aWHERE CURRENT OFclause) will update not only the table, but also the fields in the cursor variable for subsequent reads. Executing aDELETEstatement (with aWHERE CURRENT OFclause) will set all fields in the cursor variable toNULLfor subsequent readsWhen the cursor is not positioned on a row — it is positioned before the first row, or after the last row — attempts to read from the cursor variable will result in error
Cursor cursor_name is not positioned in a valid record
For checking whether all the rows of the result set have been fetched, the context variable ROW_COUNT returns the number of rows fetched by the statement.
If a record was fetched, then ROW_COUNT is one (1), otherwise zero (0).
7.7.19.1. FETCH Examples
Using the
FETCHstatement:CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMESRETURNS (RNAME CHAR(63))ASDECLARE C CURSOR FOR (SELECT RDB$RELATION_NAMEFROM RDB$RELATIONS);BEGINOPEN C;WHILE (1 = 1) DOBEGINFETCH C INTO RNAME;IF (ROW_COUNT = 0) THENLEAVE;SUSPEND;ENDCLOSE C;ENDUsing the
FETCHstatement with nested cursors:EXECUTE BLOCKRETURNS (SCRIPT BLOB SUB_TYPE TEXT)ASDECLARE VARIABLE FIELDS VARCHAR (8191);DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME;DECLARE VARIABLE RELATION RDB$RELATION_NAME;DECLARE VARIABLE SRC TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE;-- Named cursor declarationDECLARE VARIABLE CUR_R CURSOR FOR (SELECTRDB$RELATION_NAME,RDB$VIEW_SOURCEFROM RDB$RELATIONSWHERE RDB$VIEW_SOURCE IS NOT NULL);-- Declaring a named cursor in which-- a local variable is usedDECLARE CUR_F CURSOR FOR (SELECT RDB$FIELD_NAMEFROM RDB$RELATION_FIELDSWHERE-- the variable must be declared earlierRDB$RELATION_NAME =: RELATION);BEGINOPEN CUR_R;WHILE (1 = 1) DOBEGINFETCH CUR_R INTO RELATION, SRC;IF (ROW_COUNT = 0) THENLEAVE;FIELDS = NULL;-- Cursor CUR_F will use the value-- the RELATION variable initialized aboveOPEN CUR_F;WHILE (1 = 1) DOBEGINFETCH CUR_F INTO FIELD_NAME;IF (ROW_COUNT = 0) THENLEAVE;IF (FIELDS IS NULL) THENFIELDS = TRIM (FIELD_NAME);ELSEFIELDS = FIELDS || ',' || TRIM(FIELD_NAME);ENDCLOSE CUR_F;SCRIPT = 'CREATE VIEW' || RELATION;IF (FIELDS IS NOT NULL) THENSCRIPT = SCRIPT || '(' || FIELDS || ')' ;SCRIPT = SCRIPT || 'AS' || ASCII_CHAR (13);SCRIPT = SCRIPT || SRC;SUSPEND;ENDCLOSE CUR_R;ENAn example of using the
FETCHstatement with a scrollable cursor
EXECUTE BLOCKRETURNS (N INT, RNAME CHAR (63))ASDECLARE C SCROLL CURSOR FOR (SELECTROW_NUMBER() OVER (ORDER BY RDB$RELATION_NAME) AS N,RDB$RELATION_NAMEFROM RDB$RELATIONSORDER BY RDB$RELATION_NAME);BEGINOPEN C;-- move to the first record (N = 1)FETCH FIRST FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move 1 record forward (N = 2)FETCH NEXT FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move to the fifth record (N = 5)FETCH ABSOLUTE 5 FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move 1 record backward (N = 4)FETCH PRIOR FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move 3 records forward (N = 7)FETCH RELATIVE 3 FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move back 5 records (N = 2)FETCH RELATIVE -5 FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move to the first record (N = 1)FETCH FIRST FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;-- move to the last entryFETCH LAST FROM C;RNAME = C.RDB$RELATION_NAME;N = C.N;SUSPEND;CLOSE C;END
See alsoSection 7.7.4, “DECLARE .. CURSOR”, Section 7.7.18, “OPEN”, Section 7.7.20, “CLOSE”
7.7.20. CLOSE
Closes a declared cursor
Syntax
CLOSE cursor_name;
CLOSE Statement Parameter| Argument | Description |
|---|---|
cursor_name | Cursor name.
A cursor with this name must be previously declared with a |
A CLOSE statement closes an open cursor.
Only a cursor that was declared with Section 7.7.4, “DECLARE .. CURSOR” can be closed with a CLOSE statement.
Any cursors that are still open will be automatically closed after the module code completes execution.
7.7.20.1. CLOSE Examples
See Section 7.7.19.1, “FETCH Examples”
See alsoSection 7.7.4, “DECLARE .. CURSOR”, Section 7.7.18, “OPEN”, Section 7.7.19, “FETCH”
7.7.21. IN AUTONOMOUS TRANSACTION
Executes a statement or a block of statements in an autonomous transaction
Syntax
IN AUTONOMOUS TRANSACTION DO <compound_statement>
IN AUTONOMOUS TRANSACTION Statement Parameter| Argument | Description |
|---|---|
compound_statement | A single statement, or statements wrapped in |
The IN AUTONOMOUS TRANSACTION statement enables execution of a statement or a block of statements in an autonomous transaction.
Code running in an autonomous transaction will be committed right after its successful execution, regardless of the status of its parent transaction.
This can be used when certain operations must not be rolled back, even if an error occurs in the parent transaction.
An autonomous transaction has the same isolation level as its parent transaction, except for READ COMMITTED READ CONSISTENCY.
For READ COMMITTED READ CONSISTENCY, a SNAPSHOT (a.k.a. "`concurrency'") transaction is used with the same snapshot number as the outer execution context — that is, the DSQL statement that directly or indirectly invoked the current PSQL module.
Any exception that is thrown in the block of the autonomous transaction code will result in the autonomous transaction being rolled back and all changes made will be undone. If the code executes successfully, the autonomous transaction will be committed.
7.7.21.1. IN AUTONOMOUS TRANSACTION Examples
Using an autonomous transaction in a trigger for the database ON CONNECT event, to log all connection attempts, including those that failed:
CREATE TRIGGER TR_CONNECT ON CONNECTASBEGIN-- Logging all attempts to connect to the databaseIN AUTONOMOUS TRANSACTION DOINSERT INTO LOG(MSG)VALUES ('USER ' || CURRENT_USER || ' CONNECTS.');IF (EXISTS(SELECT *FROM BLOCKED_USERSWHERE USERNAME = CURRENT_USER)) THENBEGIN-- Logging that the attempt to connect-- to the database failed and sending-- a message about the eventIN AUTONOMOUS TRANSACTION DOBEGININSERT INTO LOG(MSG)VALUES ('USER ' || CURRENT_USER || ' REFUSED.');POST_EVENT 'CONNECTION ATTEMPT BY BLOCKED USER!';END-- now calling an exceptionEXCEPTION EX_BADUSER;ENDEND
See alsoTransaction Control
7.7.22. POST_EVENT
Posts an event for notification to registered clients on commit
Syntax
POST_EVENT event_name;
POST_EVENT Statement Parameter| Argument | Description |
|---|---|
event_name | Event name (message) limited to 127 bytes |
The POST_EVENT statement notifies the event manager about the event, which saves it to an event table.
When the transaction is committed, the event manager notifies applications that have registered their interest in the event.
The event name can be a code, or a short message: the choice is open as it is a string of up to 127 bytes. Keep in mind that the application listening for an event must use the exact event name when registering.
The content of the string can be a string literal, a variable or any valid SQL expression that resolves to a string.
7.7.22.1. POST_EVENT Examples
Notifying the listening applications about inserting a record into the SALES table:
CREATE TRIGGER POST_NEW_ORDER FOR SALESACTIVE AFTER INSERT POSITION 0ASBEGINPOST_EVENT 'new_order';END
7.7.23. RETURN
Returns a value from a stored function
Syntax
RETURN value;
RETURN Statement Parameter| Argument | Description |
|---|---|
value | Expression with the value to return; Can be any expression type-compatible with the return type of the function |
The RETURN statement ends the execution of a function and returns the value of the expression value.
RETURN can only be used in PSQL functions (stored functions and local sub-functions).