This chapter looks at using host tables to simplify coding and improve program performance. You learn how to manipulate Oracle data using host tables, how to operate on all the elements of a host table with a single SQL statement, how to limit the number of table elements processed, and how to use tables of group items.
The main sections are:
A host table (also known as an array) is a set of related data items, called elements, associated with a single variable. An indicator variable defined as a table is called an indicator table. An indicator table can be associated with any host table that is NULLABLE.
Host tables can ease programming and can offer greatly improved performance. When writing an application, you are usually faced with the problem of storing and manipulating large amounts of data. Host tables simplify the task of accessing multiple return values.
Host tables let you manipulate multiple rows with a single SQL statement. Thus, communications overhead is reduced markedly, especially in a networked environment. For example, suppose you want to insert information about 300 employees into the EMP table. Without host tables your program must do 300 individual INSERTs—one for each employee. With host tables, only one INSERT need be done.
Pro*COBOL allows the use of host tables in data manipulation statements. You can use host tables as input variables in the INSERT, UPDATE, and DELETE statements and as output variables in the INTO clause of SELECT and FETCH statements.
The syntax used for host tables and for simple host variables is nearly the same. One difference is the optional FOR clause, which lets you control table processing. Also, there are restrictions on mixing host tables and simple host variables in a SQL statement.
You declare and dimension host tables in the Data Division. In the following example, three host tables are declared, each dimensioned with 50 elements:
.... 01 EMP-TABLES. 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 EMP-NAME OCCURS 50 TIMES PIC X(10. 05 SALARY OCCURS 50 TIMES PIC S9(5)V99 COMP-3. ....
You can use the INDEXED BY phrase in the OCCURS clause to specify an index, as the next example shows:
... 01 EMP-TABLES. 05 EMP-NUMBER PIC X(10) OCCURS 50 TIMES INDEXED BY EMP-INDX. ... ...
The INDEXED BY phrase implicitly declares the index item EMP-INDX.
Multi-dimensional host tables are not allowed. Thus, the two-dimensional host table declared in the following example is invalid:
... 01 NATION. 05 STATE OCCURS 50 TIMES. 10 STATE-NAME PIC X(25). 10 COUNTY OCCURS 25 TIMES. 15 COUNTY-NAME PIX X(25). ...
Variable-length host tables are not allowed either. For example, the following declaration of EMP-REC is invalid for a host variable:
... 01 EMP-FILE. 05 REC-COUNT PIC S9(3) COMP. 05 EMP-REC OCCURS 0 TO 250 TIMES DEPENDING ON REC-COUNT. ...
The maximum number of host table elements in a SQL statement that is accessible in one fetch is 32K (or possibly greater, depending on your platform and the available memory). If you try to access a number that exceeds the maximum, you get a "parameter out of range" runtime error. If the statement is an anonymous PL/SQL block, the number of elements accessible is limited to 32512 divided by the size of the datatype.
If you use multiple host tables in a single SQL statement, their dimensions should be the same. This is not a requirement, however, because Pro*COBOL always uses the smallest dimension for the SQL operation. In the following example, only 25 rows are inserted
WORKING-STORAGE SECTION. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 EMP-TABLES. 05 EMP-NUMBER PIC S9(4) COMP OCCURS 50 TIMES. 05 EMP-NAME PIC X(10) OCCURS 50 TIMES. 05 DEPT-NUMBER PIC S9(4) COMP OCCURS 25 TIMES. EXEC SQL END DECLARE SECTION END-EXEC. ... PROCEDURE DIVISION. ... * Populate host tables here. ... EXEC SQL INSERT INTO EMP (EMPNO, ENAME, DEPTNO) VALUES (:EMP-NUMBER, :EMP-NAME, :DEPT-NUMBER) END-EXEC.
Host tables must not be subscripted in SQL statements. For example, the following INSERT statement is invalid:
WORKING-STORAGE SECTION. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 EMP-TABLES. 05 EMP-NUMBER PIC S9(4) COMP OCCURS 50 TIMES. 05 EMP-NAME PIC X(10) OCCURS 50 TIMES. 05 DEPT-NUMBER PIC S9(4) COMP OCCURS 50 TIMES. EXEC SQL END DECLARE SECTION END-EXEC. ... PROCEDURE DIVISION. ... PERFORM LOAD-EMP VARYING J FROM 1 BY 1 UNTIL J > 50. ... LOAD-EMP. EXEC SQL INSERT INTO EMP (EMPNO, ENAME, DEPTNO) VALUES (:EMP-NUMBER(J), :EMP-NAME(J), :DEPT-NUMBER(J)) END-EXEC.
You need not process host tables in a PERFORM VARYING statement. Instead, use the un-subscripted table names in your SQL statement. Pro*COBOL treats a SQL statement containing host tables of dimension n like the same statement executed n times with n different scalar host variables, but more efficiently.
You can use indicator tables to assign NULLs to elements in input host tables and to detect NULLs or truncated values (of character columns only) in output host tables. The following example shows how to conduct an INSERT with indicator tables:
WORKING-STORAGE SECTION. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 EMP-TABLES. 05 EMP-NUMBER PIC S9(4) COMP OCCURS 50 TIMES. 05 DEPT-NUMBER PIC S9(4) COMP OCCURS 50 TIMES. 05 COMMISSION PIC S9(5)V99 COMP-3 OCCURS 50 TIMES. 05 COMM-IND PIC S9(4) COMP OCCURS 50 TIMES. EXEC SQL END DECLARE SECTION END-EXEC. ... PROCEDURE DIVISION. ... * Populate the host and indicator tables. * Set indicator table to all zeros. ... EXEC SQL INSERT INTO EMP (EMPNO, DEPTNO, COMM) VALUES (:EMP-NUMBER, :DEPT-NUMBER, :COMMISSION:COMM-IND) END-EXEC.
The dimension of the indicator table must be greater than or equal to the dimension of the host table.
When using host table SELECT and FETCH, it is recommended that you use indicator variables. That way you can test for NULLs in the associated output host table.
If a NULL is selected or fetched into a host variable that has no associated indicator variable, your program stops processing, sets sqlca.sqlerrd(3) to the number of rows processed, and returns an error.
NULL is selected by default, but you can switch it off by using the UNSAFE_NULL = YES option.
When DBMS=V7 or V8, your program does not consider truncation to be an error.
Note: If you have a host group item containing tables, then you must use a corresponding group item of tables for an indicator. For example, if your group item is the following:
01 DEPARTURE. 05 HOUR PIC X(2) OCCURS 3 TIMES. 05 MINUTE PIC X(2) OCCURS 3 TIMES.
the following indicator variable cannot be used:
01 DEPARTURE-IND PIC S9(4) COMP OCCURS 6 TIMES.
The indicator variable you use with the group item of tables must itself be a group item of tables such as the following:
01 DEPARTURE-IND. 05 HOUR-IND PIC S9(4) COMP OCCURS 3 TIMES. 05 MINUTE-IND PIC S9(4) COMP OCCURS 3 TIMES.
You can use host tables as output variables in the SELECT statement. If you know the maximum number of rows the select will return, simply define the host tables with that number of elements. In the following example, you select directly into three host tables. The table was defined with 50 rows, with the knowledge that the select will return no more than 50 rows.
01 EMP-REC-TABLES. 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 EMP-NAME OCCURS 50 TIMES PIC X(10) VARYING. 05 SALARY OCCURS 50 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. ... EXEC SQL SELECT ENAME, EMPNO, SAL INTO :EMP-NAME, :EMP-NUMBER, :SALARY FROM EMP WHERE SAL > 1000 END-EXEC.
In this example, the SELECT statement returns up to 50 rows. If there are fewer than 50 eligible rows or you want to retrieve only 50 rows, this method will suffice. However, if there are more than 50 eligible rows, you cannot retrieve all of them this way. If you reexecute the SELECT statement, it just returns the first 50 rows again, even if more are eligible. You must either define a larger table or declare a cursor for use with the FETCH statement.
If a SELECT INTO statement returns more rows than the size of the table you defined, Oracle9i issues an error message unless you specify SELECT_ERROR=NO. For more information about the option, see "SELECT_ERROR".
Use batch fetches when the size of data you are processing is large (greater than about 100 rows) as well as when you do not know how many rows will be returned.
If you do not know the maximum number of rows a select will return, you can declare and open a cursor, and then fetch from it in "batches." Batch fetches within a loop let you retrieve a large number of rows with ease. Each fetch returns the next batch of rows from the current active set. In the following example, you fetch in 20-row batches:
... 01 EMP-REC-TABLES. 05 EMP-NUMBER OCCURS 20 TIMES PIC S9(4) COMP. 05 EMP-NAME OCCURS 20 TIMES PIC X(10) VARYING. 05 SALARY OCCURS 20 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. ... EXEC SQL DECLARE EMPCURSOR CURSOR FOR SELECT EMPNO, SAL FROM EMP END-EXEC. ... EXEC SQL OPEN EMPCURSOR END-EXEC. ... EXEC SQL WHENEVER NOT FOUND DO PERFORM END-IT. LOOP. EXEC SQL FETCH EMPCURSOR INTO :EMP-NUMBER, :SALARY END-EXEC. * -- process batch of rows ... GO TO LOOP. END-IT. ...
Do not forget to check how many rows were actually returned in the last fetch and to process them. See "Sample Program 3: Fetching in Batches" for a complete example.
For INSERT, UPDATE, and DELETE statements, SQLERRD(3) records the number of rows processed.
SQLERRD(3) is also useful when an error occurs during a table operation. Processing stops at the row that caused the error, so SQLERRD(3) gives the number of rows processed successfully.
Each fetch returns, at most, the number of entries in the table. Fewer rows are returned in the following cases:
The end of the active set is reached. The "no data found" warning code is returned to SQLCODE in the SQLCA. For example, this happens if you fetch into a table of number of entries 100, but only 20 rows are returned.
Fewer than a full batch of rows remain to be fetched. For example, this happens if you fetch 70 rows into a table of number of entries 20 because after the third fetch, only 10 rows remain to be fetched.
An error is detected while processing a row. The fetch fails and the applicable error code is returned to SQLCODE.
The cumulative number of rows returned can be found in the third element of SQLERRD in the SQLCA, called SQLERRD(3) in this guide. This applies to each open cursor. In the following example, notice how the status of each cursor is maintained separately:
EXEC SQL OPEN CURSOR1 END-EXEC. EXEC SQL OPEN CURSOR2 END-EXEC. EXEC SQL FETCH CURSOR1 INTO :TABLE-OF-20 END-EXEC. * -- now running total in SQLERRD(3) is 20 EXEC SQL FETCH CURSOR2 INTO :TABLE-OF-30 END-EXEC. * -- now running total in SQLERRD(3) is 30, not 50 EXEC SQL FETCH CURSOR1 INTO :TABLE-OF-20 END-EXEC. * -- now running total in SQLERRD(3) is 40 (20 + 20) EXEC SQL FETCH CURSOR2 INTO :TABLE-OF-30 END-EXEC. * -- now running total in SQLERRD(3) is 60 (30 + 30)
Using host tables in the WHERE clause of a SELECT statement is allowed only in a sub-query. (For an example, see "The WHERE Clause".) Also, since Pro*COBOL always takes the smallest dimension of table, do not mix simple host variables with host tables in the INTO clause of a SELECT or FETCH statement because only one row will be retrieved. If any of the host variables is a table, then all must be tables.
Table 7-1 shows which uses of host tables are valid in a SELECT INTO statement.
When UNSAFE_NULL=YES, if you select or fetch a NULL into a host table that lacks an indicator table, no error is generated. So, when doing table selects and fetches, Oracle recommends that you use indicator tables. This is because this makes it NULLs easier to find in the associated output host table. (To learn how to find NULLs and truncated values, see "Using Indicator Variables".)
When UNSAFE_NULL=NO, if you select or fetch a NULL into a host table that lacks an indicator table, Oracle9i stops processing, sets SQLERRD(3) to the number of rows processed, and issues an error message:
If you select or fetch a truncated column value into a host table that lacks an indicator table, Oracle9i sets SQLWARN(2).
You can check SQLERRD(3) for the number of rows processed before the truncation occurred. The rows-processed count includes the row that caused the truncation error.
When doing table selects and fetches, you can use indicator tables. That way, if Oracle9i assigns one or more truncated column values to an output host table, you can find the original lengths of the column values in the associated indicator table.
The following host table sample program can be found in the demo directory.
***************************************************************** * Sample Program 3: Host Tables * * * * This program logs on to ORACLE, declares and opens a cursor, * * fetches in batches using host tables, and prints the results. * ***************************************************************** IDENTIFICATION DIVISION. PROGRAM-ID. HOST-TABLES. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 USERNAME PIC X(15) VARYING. 01 PASSWD PIC X(15) VARYING. 01 EMP-REC-TABLES. 05 EMP-NUMBER OCCURS 5 TIMES PIC S9(4) COMP. 05 EMP-NAME OCCURS 5 TIMES PIC X(10) VARYING. 05 SALARY OCCURS 5 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. EXEC SQL VAR SALARY IS DISPLAY(8,2) END-EXEC. EXEC SQL END DECLARE SECTION END-EXEC. EXEC SQL INCLUDE SQLCA END-EXEC. 01 NUM-RET PIC S9(9) COMP VALUE ZERO. 01 PRINT-NUM PIC S9(9) COMP VALUE ZERO. 01 COUNTER PIC S9(9) COMP. 01 DISPLAY-VARIABLES. 05 D-EMP-NAME PIC X(10). 05 D-EMP-NUMBER PIC 9(4). 05 D-SALARY PIC Z(4)9.99. PROCEDURE DIVISION. BEGIN-PGM. EXEC SQL WHENEVER SQLERROR DO PERFORM SQL-ERROR END-EXEC. PERFORM LOGON. EXEC SQL DECLARE C1 CURSOR FOR SELECT EMPNO, SAL, ENAME FROM EMP END-EXEC. EXEC SQL OPEN C1 END-EXEC. FETCH-LOOP. EXEC SQL WHENEVER NOT FOUND DO PERFORM SIGN-OFF END-EXEC. EXEC SQL FETCH C1 INTO :EMP-NUMBER, :SALARY, :EMP-NAME END-EXEC. SUBTRACT NUM-RET FROM SQLERRD(3) GIVING PRINT-NUM. PERFORM PRINT-IT. MOVE SQLERRD(3) TO NUM-RET. GO TO FETCH-LOOP. LOGON. MOVE "SCOTT" TO USERNAME-ARR. MOVE 5 TO USERNAME-LEN. MOVE "TIGER" TO PASSWD-ARR. MOVE 5 TO PASSWD-LEN. EXEC SQL CONNECT :USERNAME IDENTIFIED BY :PASSWD END-EXEC. DISPLAY " ". DISPLAY "CONNECTED TO ORACLE AS USER: ", USERNAME-ARR. PRINT-IT. DISPLAY " ". DISPLAY "EMPLOYEE NUMBER SALARY EMPLOYEE NAME". DISPLAY "--------------- ------- -------------". PERFORM PRINT-ROWS VARYING COUNTER FROM 1 BY 1 UNTIL COUNTER > PRINT-NUM. PRINT-ROWS. MOVE EMP-NUMBER(COUNTER) TO D-EMP-NUMBER. MOVE SALARY(COUNTER) TO D-SALARY. DISPLAY " ", D-EMP-NUMBER, " ", D-SALARY, " ", EMP-NAME-ARR IN EMP-NAME(COUNTER). MOVE SPACES TO EMP-NAME-ARR IN EMP-NAME(COUNTER). SIGN-OFF. SUBTRACT NUM-RET FROM SQLERRD(3) GIVING PRINT-NUM. IF (PRINT-NUM > 0) PERFORM PRINT-IT. EXEC SQL CLOSE C1 END-EXEC. EXEC SQL COMMIT WORK RELEASE END-EXEC. DISPLAY " ". DISPLAY "HAVE A GOOD DAY.". DISPLAY " ". STOP RUN. SQL-ERROR. EXEC SQL WHENEVER SQLERROR CONTINUE END-EXEC. DISPLAY " ". DISPLAY "ORACLE ERROR DETECTED:". DISPLAY " ". DISPLAY SQLERRMC. EXEC SQL ROLLBACK WORK RELEASE END-EXEC. STOP RUN.
You can use host tables as input variables in an INSERT statement. Just make sure your program populates the tables with data before executing the INSERT statement. If some elements in the tables are irrelevant, you can use the FOR clause to control the number of rows inserted. See "The FOR Clause".
An example of inserting with host tables follows:
01 EMP-REC-TABLES. 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 EMP-NAME OCCURS 50 TIMES PIC X(10) VARYING. 05 SALARY OCCURS 50 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. * -- populate the host tables EXEC SQL INSERT INTO EMP (ENAME, EMPNO, SAL) VALUES (:EMP-NAME, :EMP-NUMBER, :SALARY) END-EXEC.
The number of rows inserted will be available in SQLERRD(3).
Host tables must not be subscripted in SQL statements. For example the following INSERT statement is invalid:
PERFORM VARYING I FROM 1 BY 1 UNTIL I = TABLE-DIMENSION. EXEC SQL INSERT INTO EMP (ENAME, EMPNO, SAL) VALUES (:EMP-NAME(I), :EMP-NUMBER(I), :SALARY(I)) END_EXEC END-PERFORM.
Mixing simple host variables with host tables in the VALUES clause of an INSERT, UPDATE, or DELETE statement causes only the first element of any host table to be processed because simple host variables are treated as host tables of dimension one and Pro*COBOL always uses the smallest declared dimension. You receive a warning when this occurs.
You can also use host tables as input variables in an UPDATE statement, as the following example shows:
01 EMP-REC-TABLES. 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 SALARY OCCURS 50 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. ... * -- populate the host tables EXEC SQL UPDATE EMP SET SAL = :SALARY WHERE EMPNO = :EMP-NUMBER END-EXEC.
The number of rows updated by issuing this statement is available in SQLERRD(3). This is not necessarily the number of rows in the host table. The number does not include rows processed by an update cascade (which causes subsequent updates.)
If some elements in the tables are irrelevant, you can use the FOR clause to limit the number of rows updated.
The last example showed a typical update using a unique key (EMP-NUMBER). Each table element qualified just one row for updating. In the following example, each table element qualifies multiple rows:
EXEC SQL BEGIN DECLARE SECTION END-EXEC. ... 05 JOB-TITLE OCCURS 10 TIMES PIC X(10) VARYING. 05 COMMISSION OCCURS 50 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. EXEC SQL END DECLARE SECTION END-EXEC. * -- populate the host tables EXEC SQL UPDATE EMP SET COMM = :COMMISSION WHERE JOB = :JOB-TITLE END-EXEC.
You cannot use host tables with the CURRENT OF clause in an UPDATE statement. For an alternative, see "Mimicking the CURRENT OF Clause".
Table 7-2 shows which uses of host tables are valid in an UPDATE statement:
You can also use host tables as input variables in a DELETE statement. Doing so is like executing the DELETE statement repeatedly using successive elements of the host table in the WHERE clause. Thus, each execution might delete zero, one, or more rows from the table. An example of deleting with host tables follows:
EXEC SQL BEGIN DECLARE SECTION END-EXEC. ... 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. EXEC SQL END DECLARE SECTION END-EXEC. * -- populate the host table EXEC SQL DELETE FROM EMP WHERE EMPNO = :EMP-NUMBER END-EXEC.
The cumulative number of rows deleted can be found in SQLERRD(3). That number does not include rows processed by a delete cascade.
The last example showed a typical delete using a unique key (EMP-NUMBER). Each table element qualified just one row for deletion. In the following example, each table element qualifies multiple rows:
EXEC SQL BEGIN DECLARE SECTION END-EXEC. ... 05 JOB-TITLE OCCURS 10 TIMES PIC X(10) VARYING. EXEC SQL END DECLARE SECTION END-EXEC. * -- populate the host table EXEC SQL DELETE FROM EMP WHERE JOB = :JOB-TITLE END-EXEC.
You cannot use host tables with the CURRENT OF clause in a DELETE statement. For an alternative, see "Mimicking the CURRENT OF Clause".
You use indicator tables to assign NULLs to input host tables and to detect NULL or truncated values in output host tables. The following example shows how to insert with indicator tables:
01 EMP-REC-VARS. 05 EMP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 DEPT-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 COMMISSION OCCURS 50 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. * -- indicator table: 05 COMM-IND OCCURS 50 TIMES PIC S9(4) COMP. * -- populate the host tables * -- populate the indicator table; to insert a NULL into * -- the COMM column, assign -1 to the appropriate element in * -- the indicator table EXEC SQL INSERT INTO EMP (EMPNO, DEPTNO, COMM) VALUES (:EMP_NUMBER, :DEPT-NUMBER, :COMMISSION:COMM-IND) END-EXEC.
The number of entries of the indicator table cannot be smaller than the number of entries of the host table.
You can use the optional FOR clause to set the number of table elements processed by any of the following SQL statements:
DELETE
EXECUTE (See information on Oracle dynamic SQL in Chapter 11, "Oracle Dynamic SQL: Method 4".
FETCH
INSERT
OPEN
UPDATE
The FOR clause is especially useful in UPDATE, INSERT, and DELETE statements. With these statements you might not want to use the entire table. The FOR clause lets you limit the elements used to just the number you need, as the following example shows:
EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 EMP-REC-VARS. 05 EMP-NAME OCCURS 1000 TIMES PIC X(20) VARYING. 05 SALARY OCCURS 100 TIMES PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. 01 ROWS-TO-INSERT PIC S9(4) COMP. EXEC SQL END DECLARE SECTION END-EXEC. * -- populate the host tables MOVE 25 TO ROWS-TO-INSERT. * -- set FOR-clause variable * -- will process only 25 rows EXEC SQL FOR :ROWS-TO-INSERT INSERT INTO EMP (ENAME, SAL) VALUES (:EMP-NAME, :SALARY) END-EXEC.
The FOR clause must use an integer host variable to count table elements. For example, the following statement is illegal:
* -- illegal EXEC SQL FOR 25 INSERT INTO EMP (ENAME, EMPNO, SAL) VALUES (:EMP-NAME, :EMP-NUMBER, :SALARY) END-EXEC.
The FOR clause variable specifies the number of table elements to be processed. Make sure the number does not exceed the smallest table dimension. Internally, the value is treated as an unsigned quantity. An attempt to pass a negative value through the use of a signed host variable will result in unpredictable behavior.
Two restrictions keep FOR clause semantics clear: you cannot use the FOR clause in a SELECT statement or with the CURRENT OF clause.
If you use the FOR clause in a SELECT statement, you receive an error message.
The FOR clause is not allowed in SELECT statements because its meaning is unclear. Does it mean "execute this SELECT statement n times"? Or, does it mean "execute this SELECT statement once, but return n rows"? The problem in the former case is that each execution might return multiple rows. In the latter case, it is better to declare a cursor and use the FOR clause in a FETCH statement, as follows:
EXEC SQL FOR :LIMIT FETCH EMPCURSOR INTO ...
You can use the CURRENT OF clause in an UPDATE or DELETE statement to refer to the latest row returned by a FETCH statement, as the following example shows:
EXEC SQL DECLARE EMPCURSOR CURSOR FOR SELECT ENAME, SAL FROM EMP WHERE EMPNO = :EMP-NUMBER END-EXEC. ... EXEC SQL OPEN EMPCURSOR END-EXEC. ... EXEC SQL FETCH emp_cursor INTO :EM-NAME, :SALARY END-EXEC. ... EXEC SQL UPDATE EMP SET SAL = :NEW-SALARY WHERE CURRENT OF EMPCURSOR END-EXEC.
However, you cannot use the FOR clause with the CURRENT OF clause. The following statements are invalid because the only logical value of LIMIT is 1 (you can only update or delete the current row once):
EXEC SQL FOR :LIMIT UPDA-CURSOR END-EXEC. ... EXEC SQL FOR :LIMIT DELETE FROM EMP WHERE CURRENT OF emp_cursor END-EXEC.
Pro*COBOL treats a SQL statement containing host tables of number of entries n like the same SQL statement executed n times with n different scalar variables (the individual table elements). The precompiler issues an error message only when such treatment is ambiguous:
For example, assuming the declarations:
EXEC SQL BEGIN DECLARE SECTION END-EXEC. ... 05 MGRP-NUMBER OCCURS 50 TIMES PIC S9(4) COMP. 05 JOB-TITLE OCCURS 50 TIMES PIC X(20) VARYING. 01 I PIC S9(4) COMP. EXEC SQL END DECLARE SECTION END-EXEC.
it would be ambiguous if the statement:
EXEC SQL SELECT MGR INTO :MGR-NUMBER FROM EMP WHERE JOB = :JOB-TITLE END-EXEC.
were treated like the following statement
PERFORM VARYING I FROM 1 BY 1 UNTIL I = 50 SELECT MGR INTO :MGR-NUMBER(I) FROM EMP WHERE JOB = :JOB_TITLE(I) END-EXEC END-PERFORM.
because multiple rows might meet the WHERE-clause search condition, but only one output variable is available to receive data. Therefore, an error message is issued.
On the other hand, it would not be ambiguous if the statement
EXEC SQL UPDATE EMP SET MGR = :MGR_NUMBER WHERE EMPNO IN (SELECT EMPNO FROM EMP WHERE JOB = :JOB-TITLE) END-EXEC.
were treated like the following statement
PERFORM VARYING I FROM 1 BY 1 UNTIL I = 50
UPDATE EMP SET MGR = :MGR_NUMBER(I)
WHERE EMPNO IN
(SELECT EMPNO FROM EMP WHERE JOB = :JOB-TITLE(I))
END-EXEC
END-PERFORM.
because there is a MGR-NUMBER in the SET clause for each row matching JOB-TITLE in the WHERE clause, even if each JOB-TITLE matches multiple rows. All rows matching each JOB-TITLE can be SET to the same MGR-NUMBER, so no error message is issued.
The CURRENT OF clause enables you to do UPDATEs or DELETEs of the most recent row in the cursor. Use of the CURRENT OF clause causes the FOR UPDATE clause to be added to the cursor. Adding this clause has the effect of locking all rows identified by the cursor in exclusive mode. Note that you cannot use CURRENT OF with host tables. Instead, append FOR UPDATE to the definition of the cursor and explicitly select the ROWID column, then use that value to identify the current row during the update or delete. An example follows:
05 EMP-NAME OCCURS 25 TIMES PIC X(20) VARYING. 05 JOB-TITLE OCCURS 25 TIMES PIC X(15) VARYING. 05 OLD-TITLE OCCURS 25 TIMES PIC X(15) VARYING. 05 ROW-ID OCCURS 25 TIMES PIC X(18) VARYING. ... EXEC SQL DECLARE EMPCURSOR CURSOR FOR SELECT ENAME, JOB, ROWID FROM EMP FOR UPDATE END-EXEC. ... EXEC SQL OPEN EMPCURSOR END-EXEC. ... EXEC SQL WHENEVER NOT FOUND GOTO ... ... PERFORM EXEC SQL FETCH EMPCURSOR INTO :EMP-NAME, :JOB-TITLE, :ROW-ID END-EXEC ... EXEC SQL DELETE FROM EMP WHERE JOB = :OLD-TITLE AND ROWID = :ROW-ID END-EXEC EXEC SQL COMMIT WORK END-EXEC END-PERFORM.
Pro*COBOL allows the use of tables of group items (also called records) in embedded SQL statements. The tables of group items can be referenced in the INTO clause of a SELECT or a FETCH statement, and in the VALUES list of an INSERT statement.
For example, given the following declaration:
01 TABLES. 05 EMP-TABLE OCCURS 20 TIMES. 10 EMP-NUMBER PIC S9(4) COMP. 10 EMP-NAME PIC X(10). 10 DEPT-NUMBER PIC S9(4) COMP.
the following statement is valid:
EXEC SQL INSERT INTO EMP(EMPNO, ENAME, DEPTNO) VALUES(:EMP-TABLE) END-EXEC.
Assuming that the group item has been filled with data already, the statement bulk inserts 20 rows consisting of the employee number, employee name, and department number into the EMP table.
Make sure that the order of the group items corresponds to the order in the SQL statement.
To use an indicator variable, set up a second table of a group item that contains an indicator variable for each variable in the group item:
01 TABLES-IND. 05 EMP-TABLE-IND OCCURS 20 TIMES. 10 EMP-NUMBER-IND PIC S9(4) COMP. 10 EMP-NAME-IND PIC S9(4) COMP. 10 DEPT-NUMBER_IND PIC S9(4) COMP.
The host indicator table of a group item can be used as follows:
EXEC SQL INSERT INTO EMP (EMPNO, ENAME, DEPTNO) VALUES (:EMP-TABLE:EMP-TABLE-IND) END-EXEC.
If the exact characteristics of the data are known, it is convenient to specify an elementary item indicator for a group item:
05 EMP-TABLE-IND PIC S9(4) COMP OCCURS 20 TIMES.
Host tables of group items cannot have group items that are tables. For example:
01 TABLES. 05 EMP-TABLE OCCURS 20 TIMES. 10 EMP-NUMBER PIC S9(4) COMP OCCURS 10 TIMES. 10 EMP-NAME PIC X(10). 10 DEPT-NUMBER PIC S9(4) COMP.
EMP-TABLE
cannot be used as a host variable because EMP-NUMBER
is a table.
Host tables of nested group items are not allowed. For example:
01 TABLES. 05 TEAM-TABLE OCCURS 20 TIMES 10 EMP-TABLE 15 EMP-NUMBER PIC S9(4) COMP. 15 EMP-NAME PIC X(10). 10 DEPT-TABLE. 15 DEPT-NUMBER PIC S9(4) COMP. 15 DEPT-NAME PIC X(10).
TEAM-TABLE
cannot be used as a host variable because its members (EMP-TABLE
and DEPT-TABLE
) are group items themselves.
Finally, the restrictions that apply to host tables in Pro*COBOL also apply to tables of group items:
Multi-dimensional and variable-length tables are not allowed.
If multiple tables are used in a single SQL statement, their dimensions should be the same.
Host tables in SQL statements must not be subscripted.
This program logs on, declares and opens a cursor, fetches in batches using a table of group items. Read the initial comments for details.
***************************************************************** * Sample Program 14: Tables of group items * * * * This program logs on to ORACLE, declares and opens a cursor, * * fetches in batches using a table of group items , and prints * * the results. This sample is identical to sample3 except that * * instead of using three separate host tables of five elements * * each, it uses a five-element table of three group items. * * The output should be identical. * ***************************************************************** IDENTIFICATION DIVISION. PROGRAM-ID. TABLE-OF-GROUP-ITEMS. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 USERNAME PIC X(15) VARYING. 01 PASSWD PIC X(15) VARYING. 01 EMP-REC-TABLE OCCURS 5 TIMES. 05 EMP-NUMBER PIC S9(4) COMP. 05 SALARY PIC S9(6)V99 DISPLAY SIGN LEADING SEPARATE. 05 EMP-NAME PIC X(10) VARYING. EXEC SQL VAR SALARY IS DISPLAY(8,2) END-EXEC. EXEC SQL END DECLARE SECTION END-EXEC. EXEC SQL INCLUDE SQLCA END-EXEC. 01 NUM-RET PIC S9(9) COMP VALUE ZERO. 01 PRINT-NUM PIC S9(9) COMP VALUE ZERO. 01 COUNTER PIC S9(9) COMP. 01 DISPLAY-VARIABLES. 05 D-EMP-NAME PIC X(10). 05 D-EMP-NUMBER PIC 9(4). 05 D-SALARY PIC Z(4)9.99. PROCEDURE DIVISION. BEGIN-PGM. EXEC SQL WHENEVER SQLERROR DO PERFORM SQL-ERROR END-EXEC. PERFORM LOGON. EXEC SQL DECLARE C1 CURSOR FOR SELECT EMPNO, SAL, ENAME FROM EMP END-EXEC. EXEC SQL OPEN C1 END-EXEC. FETCH-LOOP. EXEC SQL WHENEVER NOT FOUND DO PERFORM SIGN-OFF END-EXEC. EXEC SQL FETCH C1 INTO :EMP-REC-TABLE END-EXEC. SUBTRACT NUM-RET FROM SQLERRD(3) GIVING PRINT-NUM. PERFORM PRINT-IT. MOVE SQLERRD(3) TO NUM-RET. GO TO FETCH-LOOP. LOGON. MOVE "SCOTT" TO USERNAME-ARR. MOVE 5 TO USERNAME-LEN. MOVE "TIGER" TO PASSWD-ARR. MOVE 5 TO PASSWD-LEN. EXEC SQL CONNECT :USERNAME IDENTIFIED BY :PASSWD END-EXEC. DISPLAY " ". DISPLAY "CONNECTED TO ORACLE AS USER: ", USERNAME-ARR. PRINT-IT. DISPLAY " ". DISPLAY "EMPLOYEE NUMBER SALARY EMPLOYEE NAME". DISPLAY "--------------- ------- -------------". PERFORM PRINT-ROWS VARYING COUNTER FROM 1 BY 1 UNTIL COUNTER > PRINT-NUM. PRINT-ROWS. MOVE EMP-NUMBER(COUNTER) TO D-EMP-NUMBER. MOVE SALARY(COUNTER) TO D-SALARY. DISPLAY " ", D-EMP-NUMBER, " ", D-SALARY, " ", EMP-NAME-ARR IN EMP-NAME(COUNTER). MOVE SPACES TO EMP-NAME-ARR IN EMP-NAME(COUNTER). SIGN-OFF. SUBTRACT NUM-RET FROM SQLERRD(3) GIVING PRINT-NUM. IF (PRINT-NUM > 0) PERFORM PRINT-IT. EXEC SQL CLOSE C1 END-EXEC. EXEC SQL COMMIT WORK RELEASE END-EXEC. DISPLAY " ". DISPLAY "HAVE A GOOD DAY.". DISPLAY " ". STOP RUN. SQL-ERROR. EXEC SQL WHENEVER SQLERROR CONTINUE END-EXEC. DISPLAY " ". DISPLAY "ORACLE ERROR DETECTED:". DISPLAY " ". DISPLAY SQLERRMC. EXEC SQL ROLLBACK WORK RELEASE END-EXEC. STOP RUN.