Back to Contents


Database Result Set Processing

Summary:

See also: Transactions, Positioned Updates, Static SQL, Dynamic SQL, SQL Errors.


What is a database result set?

A Database Result Set is a set of rows generated by an SQL statement producing rows (like SELECT). The result set is maintained by the database server. In a program, you handle a result set with a Database Cursor.

First, you must declare the database cursor with the DECLARE instruction. This instruction sends the SQL statement to the database server for parsing, validation and to generate the execution plan.

The result set is produced after execution of the SQL statement, when the database cursor is associated with the result set by the OPEN instruction. At this point, no data rows are transmitted to the program. You must use the FETCH instruction to retrieve data rows from the database server.

When finished with the result set processing, you must CLOSE the cursor to release the resources allocated for the result set on the database server. The cursor can be re-opened if needed. If the SQL statement is no longer needed, you can free the resources allocated to the statement execution with the FREE instruction.

The scope of reference of a database cursor is local to a module, so a cursor that was declared in one source file cannot be referenced from a statement in another file.

The language supports sequential and scrollable cursors. Sequential cursors, which are unidirectional, are used to retrieve rows to be printed in a report, for example.  Scrollable cursors allow you to move backwards or to an absolute or relative position in the result set. You specify whether a cursor is scrollable with the SCROLL option of the DECLARE instruction.


DECLARE

Purpose:

This instruction associates a database cursor with an SQL statement in the current connection.

Syntax 1: Cursor declared with a static SQL statement.

DECLARE cid [SCROLL] CURSOR [WITH HOLD] FOR select-statement

Syntax 2: Cursor declared with a prepared statement.

DECLARE cid [SCROLL] CURSOR [WITH HOLD] FOR sid

Syntax 3: Cursor declared with a string expression.

DECLARE cid [SCROLL] CURSOR [WITH HOLD] FROM expr

Notes:

  1. cid is the identifier of the database cursor.
  2. select-statement is a SELECT statement defined in Static SQL.
  3. sid is the identifier of a prepared SQL statement.
  4. expr is any expression that evaluates to a string.
  5. The SQL statement is parsed, validated and the execution plan is created.
  6. In all supported syntaxes, it is possible to use the ? question mark as a parameter placeholder. 
  7. DECLARE must precede any other statement that refers to the cursor during program execution.
  8. The scope of reference of the cid cursor identifier is local to the module where it is declared.
  9. Use the SCROLL option to declare scrollable cursors.
  10. Use the WITH HOLD option to declare cursors that have uninterrupted access to rows across multiple transactions. Informix only!
  11. Resources allocated by the DECLARE can be released later by the FREE instruction.

Warnings:

  1. The number of declared cursors in a single program is limited by the database server and the available memory. Make sure that you free the resources when you no longer need the declared cursor.
  2. The identifier of a cursor that was declared in one module cannot be referenced from another module.
  3. When declaring a cursor with a static select-statement, the statement can include an INTO clause. However, this is not recommended, to be consistent with prepared statements.  If you prepare the statement, you must omit the INTO clause in the SQL text provided to the PREPARE instruction and use the INTO clause of the FETCH statement to retrieve the values from the result set.
  4. You can add the FOR UPDATE clause in the SELECT statement to declare an update cursor. You can use the update cursor to modify (update or delete) the current row.
  5. Use the WITH HOLD option carefully, because this feature is specific to IBM Informix servers. Other database servers do not behave as  Informix does with such cursors. For example, if the SELECT is not declared FOR UPDATE, most database servers keep cursors open after the end of a transaction, but IBM DB2 automatically closes all cursors when the transaction is rolled back.

Usage:

If you use only the DECLARE CURSOR keywords, you create a sequential cursor, which can fetch only the next row in sequence from the result set. The sequential cursor can read through the result set only once each time it is opened. If you are using a sequential cursor for a select cursor, on each execution of the FETCH statement, the database server returns the contents of the current row and locates the next row in the result set.

Example 1: Declaring a cursor with a static SELECT statement.

01 MAIN
02    DATABASE stores
03    DECLARE c1 CURSOR FOR SELECT * FROM customer
04 END MAIN

Example 2: Declaring a cursor with a prepared statement.

01 MAIN
02   DEFINE key INTEGER
03   DEFINE cust RECORD
04            num INTEGER,
05            name CHAR(50)
06          END RECORD
07   DATABASE stores
08   PREPARE s1
09      FROM "SELECT customer_num, cust_name FROM customer WHERE customer_num>?"
10   DECLARE c1 CURSOR FOR s1
11   LET key=101
12   FOREACH c1 USING key INTO cust.*
13      DISPLAY cust.*
14   END FOREACH
15 END MAIN

Use the DECLARE SCROLL CURSOR keywords to create a scrollable cursor, which can fetch rows of the result set in any sequence.  Until the cursor is closed, the database server retains the result set of the cursor in a static data set (for example like Informix, in a temporary table). You can fetch the first, last, or any intermediate rows of the result set as well as fetch rows repeatedly without having to close and reopen the cursor. On a multi-user system, the rows in the tables from which the result set rows were derived might change after the cursor is opened and a copy is made in the static data set. If you use a scroll cursor within a transaction, you can prevent copied rows from changing either by setting the isolation level to Repeatable Read or by locking the entire table in share mode during the transaction. Scrollable cursors cannot be declared FOR UPDATE.

The DECLARE [SCROLL] CURSOR FROM syntax allows you to declare a cursor directly with a string expression, so that you do not have to use the PREPARE instruction. This simplifies the source code and speeds up the execution time for non-Informix databases, because the SQL statement is not parsed twice.

Example 3: Declaring a scrollable cursor with string expression.

01 MAIN
02   DEFINE key INTEGER
03   DEFINE cust RECORDs
04            num INTEGER,
05            name CHAR(50)
06          END RECORD
07   DATABASE stores
08   DECLARE c1 SCROLL CURSOR
09      FROM "SELECT customer_num, cust_name FROM customer WHERE customer_num>?"
10   LET key=101
11   FOREACH c1 USING key INTO cust.*
12      DISPLAY cust.*
13   END FOREACH
14 END MAIN

Informix only: Use the WITH HOLD option to create a hold cursor. A hold cursor allows uninterrupted access to a set of rows across multiple transactions. Ordinarily, all cursors close at the end of a transaction. A hold cursor does not close; it remains open after a transaction ends. A hold cursor can be either a sequential cursor or a scrollable cursor. Hold cursors are only supported by Informix database engines.

You can use the ? question mark place holders with prepared or static SQL statements, in order to provide the parameters at execution time, with the USING clause of the OPEN or FOREACH instructions.

Example 4: Declaring a hold cursor with ? parameter place holders.

01 MAIN
02   DEFINE key INTEGER
03   DEFINE cust RECORDs
04            num INTEGER,
05            name CHAR(50)
06          END RECORD
07   DATABASE stores
08   DECLARE c1 CURSOR WITH HOLD
09      FOR SELECT customer_num, cust_name FROM customer WHERE customer_num > ?
10   LET key=101
11   FOREACH c1 USING key INTO cust.*
12      BEGIN WORK
13      UPDATE cust2 SET name=cust.cust_name WHERE num=cust.num
14      COMMIT WORK
15   END FOREACH
16 END MAIN

OPEN

Purpose:

Executes the SQL statement associated with a database cursor declared in the same connection.

Syntax:

OPEN cid
  
[ USING paramvar [,...] ]
   [ WITH REOPTIMIZATION ]

Notes:

  1. cid is the identifier of the database cursor.
  2. The USING clause is required to provide the SQL parameter buffers, if the cursor was declared with a prepared statement that includes (?) question mark placeholders.
  3. paramvar is a program variable, a record or an array used as a parameter buffer to provide SQL parameter values.
  4. Sometimes, query execution plans need to be re-optimized when SQL parameter values change. Use the WITH REOPTIMIZATION clause to indicate that the query execution plan has to be re-optimized on the database server (this operation is normally done during the DECLARE instruction).  If this option is not supported by the database server, it is ignored.
  5. A subsequent OPEN statement closes the cursor and then reopens it. When the database server reopens the cursor, it creates a new result set, based on the current values of the variables in the USING clause. If the variables have changed since the previous OPEN statement, reopening the cursor can generate an entirely different result set.
  6. In a IBM Informix database that is ANSI-compliant, you receive an error code if you try to open a cursor that is already open. Informix only!
  7. With the CLOSE instruction, you can release resources allocated for the result set on the database server.

Warnings:

  1. The database server evaluates the values that are named in the USING clause of the OPEN statement only when it opens the cursor. While the cursor is open, subsequent changes to program variables in the OPEN clause do not change the result set of the cursor; you must re-open the cursor to re-execute the statement.
  2. If you release cursor resources with a FREE instruction, you cannot use the cursor unless you declare the cursor again.

Example:

01 MAIN
02    DATABASE stores
03    DECLARE c1 CURSOR FOR SELECT * FROM customer
04    OPEN c1
05 END MAIN

FETCH

Purpose:

Moves a cursor to a new row in the corresponding result set and retrieves the row values into fetch buffers.

Syntax:

FETCH [ direction ] cid [ INTO fetchvar [,...] ]

where direction is one of:

{
  NEXT
| { PREVIOUS | PRIOR }
| CURRENT
| FIRST
| LAST
| ABSOLUTE position
| RELATIVE offset
}

Notes:

  1. cid is the identifier of the database cursor.
  2. The INTO clause can be used to provide the fetch buffers that receive the row values.
  3. fetchvar is a program variable, a record or an array used as a fetch buffer to receive a row value.
  4. A sequential cursor can fetch only the next row in sequence from the result set.
  5. direction options different from NEXT can only be used with scrollable cursors.
  6. position is an positive integer expression.
  7. offset is a positive or negative integer expression.
  8. The NEXT clause (the default) retrieves the next row in the result set. If the row pointer was on the last row before executing the instruction, the SQL Code is set to 100 (NOTFOUND), and the row pointer remains on the last row. (if you issue a FETCH PREVIOUS at this time, you get the next-to-last row).
  9. The PREVIOUS clause retrieves the previous row in the result set. If the row pointer was on the first row before executing the instruction, the SQL Code is set to 100 (NOTFOUND), and the row pointer remains on the first row. (if you issue a FETCH NEXT at this time, you get the second row).
  10. The CURRENT clause retrieves the current row in the result set.
  11. The FIRST clause retrieves the first row in the result set.
  12. The LAST clause retrieves the last row in the result set.
  13. The ABSOLUTE clause retrieves the row at position in the result set. If the position is not correct, the SQL Code is set to 100 (NOTFOUND). Absolute row positions are numbered from 1.
  14. The RELATIVE clause moves offset rows in the result set and returns the row at the current position. The offset can be a negative value.  If the offset is not correct, the SQL Code is set to 100 (NOTFOUND). If offset is zero, the current row is fetched.

Warnings:

  1. Fetching rows can have specific behavior when the cursor was declared FOR UPDATE. See Positioned Updates for more details.

Example:

01 MAIN
02    DEFINE cnum INTEGER
03    DEFINE cname CHAR(20)
04    DATABASE stores
05    DECLARE c1 SCROLL CURSOR FOR SELECT customer_num, cust_name FROM customer
06    OPEN c1
07    FETCH c1 INTO cnum, cname
08    FETCH LAST c1 INTO cnum, cname
09    FETCH PREVIOUS c1 INTO cnum, cname
10    FETCH FIRST c1 INTO cnum, cname
11    FETCH LAST c1
12    FETCH FIRST c1
13 END MAIN

CLOSE

Purpose:

Closes a database cursor and frees resources allocated on the database server for the result set.

Syntax:

CLOSE cid

Notes:

  1. cid is the identifier of the database cursor.
  2. This instruction releases the resources allocated for the result set on the database server.
  3. After using the CLOSE instruction, you must re-open the cursor with OPEN before retrieving values with FETCH.

Example:

01 MAIN
02    DATABASE stores
03    DECLARE c1 CURSOR FOR SELECT * FROM customer
04    OPEN c1
05    CLOSE c1
06    OPEN c1
07    CLOSE c1
08 END MAIN

FREE

Purpose:

This instruction releases resources allocated to the database cursor with the DECLARE instruction.

Syntax:

FREE cid

Notes:

  1. cid is the identifier of the database cursor.
  2. All resources allocated to the database cursor are released.
  3. The cursor should be explicitly closed before it is freed.

Warnings:

  1. If you release cursor resources with this instruction, you cannot use the cursor unless you declare the cursor again.

Example:

01 MAIN
02    DEFINE i, j INTEGER
03    DATABASE stores
04    FOR i=1 TO 10
05        DECLARE c1 CURSOR FOR SELECT * FROM customer
06        FOR j=1 TO 10
07            OPEN c1
08            FETCH c1
09            CLOSE c1
10        END FOR
11        FREE c1
12    END FOR
13 END MAIN

FOREACH

Purpose:

A FOREACH block applies a series of actions to each row of data that is returned from a database cursor.

Syntax:

FOREACH cid
   [ USING paramvar [,...] ]
   [ INTO fetchvar [,...] ]
   [ WITH REOPTIMIZATION ]
       {
         statement
       | CONTINUE FOREACH
       | EXIT FOREACH
       }
       [...]
END FOREACH

Notes:

  1. cid is the identifier of the database cursor.
  2. As with the OPEN instruction, the cursor must be declared before it is used in a FOREACH instruction.
  3. The USING clause is required to provide the SQL parameter buffers, if the cursor was declared with a prepared statement that includes (?) question mark placeholders.
  4. paramvar is a program variable, a record or an array used as a parameter buffer to provide SQL parameter values.
  5. The INTO clause can be used to provide the fetch buffers that receive the row values.
  6. fetchvar is a program variable, a record or an array used as a fetch buffer to receive a row value.
  7. Use the WITH REOPTIMIZATION clause to indicate that the query execution plan has to be re-optimized.
  8. statement is any statement supported by the language.
  9. The CONTINUE FOREACH instruction interrupts processing of the current row and starts processing the next row. The runtime system fetches the next row and resumes processing at the first statement in the block.
  10. The EXIT FOREACH instruction interrupts processing and ignores the remaining rows of the result set.

Warnings:

  1. Infinite loops may occur if the cursor preparation failed. 

Usage:

Use the FOREACH instruction to retrieve and process database rows that were selected by a query. This instruction is equivalent to using the OPEN, FETCH, and CLOSE cursor instructions:

  1. Open the specified cursor
  2. Fetch the rows selected
  3. Close the cursor (after the last row has been fetched)

You must declare the cursor (by using the DECLARE instruction) before the FOREACH instruction can retrieve the rows. A compile-time error occurs unless the cursor was declared prior to this point in the source module. You can reference a sequential cursor, a scroll cursor, a hold cursor, or an update cursor, but FOREACH only processes rows in sequential order.

The FOREACH statement performs successive fetches until all rows specified by the SELECT statement are retrieved. Then the cursor is automatically closed. It is also closed if a WHENEVER NOT FOUND exception handler within the FOREACH loop detects a NOTFOUND condition (that is, SQL Code = 100).

Example:

01 MAIN
02    DEFINE clist ARRAY[200] OF RECORD
03            cnum INTEGER,
04            cname CHAR(50)
05           END RECORD
06    DEFINE i INTEGER
07    DATABASE stores
08    DECLARE c1 CURSOR FOR SELECT customer_num, cust_name FROM customer
09    LET i=0
10    FOREACH c1 INTO clist[i+1].*
11       LET i=i+1
12       DISPLAY clist[i].*
13    END FOREACH
14    DISPLAY "Number of rows found: ", i
15 END MAIN