Suppose your application needs to be able to handle a dynamic SELECT statement, one that changes from one use to the next. This statement can be read from a display, passed in from another application, or built dynamically by your application.
In other words, you don't know exactly what this statement is going to be returning every time. Your application needs to be able to handle the varying number of result columns with data types that are unknown ahead of time.
For example, the following statement needs to be processed:
SELECT WORKDEPT, PHONENO FROM CORPDATA.EMPLOYEE WHERE LASTNAME = 'PARKER'
The statement is assigned to a host variable. The host variable, in this case named DSTRING, is then processed by using the PREPARE statement as shown:
EXEC SQL PREPARE S1 FROM :DSTRING;
Next, you need to determine the number of result columns and their data types. To do this, you need an SQLDA.
The first step in defining an SQLDA, is to allocate storage for it. (Allocating storage is not necessary in REXX.) The techniques for acquiring storage are language dependent. The SQLDA must be allocated on a 16-byte boundary. The SQLDA consists of a fixed-length header that is 16 bytes in length. The header is followed by a varying-length array section (SQLVAR), each element of which is 80 bytes in length.
The amount of storage that you need to allocate depends on how many elements you want to have in the SQLVAR array. Each column you select must have a corresponding SQLVAR array element. Therefore, the number of columns listed in your SELECT statement determines how many SQLVAR array elements you should allocate. Since this SELECT statement was specified at run time, it is impossible to know exactly how many columns will be accessed. Consequently, you must estimate the number of columns. Suppose, in this example, that no more than 20 columns are ever expected to be accessed by a single SELECT statement. In this case, the SQLVAR array should have a dimension of 20, ensuring that each item in the select-list has a corresponding entry in SQLVAR. This makes the total SQLDA size 20 x 80, or 1600, plus 16 for a total of 1616 bytes
Having allocated what you estimated to be enough space for your SQLDA, you need to set the SQLN field of the SQLDA equal to the number of SQLVAR array elements, in this case 20.
Having allocated storage and initialized the size, you can now issue a DESCRIBE statement.
EXEC SQL DESCRIBE S1 INTO :SQLDA;
When the DESCRIBE statement is run, SQL places values in the SQLDA that provide information about the select-list for your statement. The following tables show the contents of the SQLDA after the DESCRIBE is run. Only the entries that are meaningful in this context are shown.
The SQLDA header contains:
Description | Value |
---|---|
SQLAID | 'SQLDA' |
SQLDABC | 1616 |
SQLN | 20 |
SQLD | 2 |
SQLDAID is an identifier field initialized by SQL when a DESCRIBE is run. SQLDABC is the byte count or size of the SQLDA. The SQLDA header is followed by 2 occurrences of the SQLVAR structure, one for each column in the result table of the SELECT statement being described:
Description | Value |
---|---|
SQLTYPE | 453 |
SQLLEN | 3 |
SQLDATA (3:4) | 37 |
SQLNAME | 8 WORKDEPT |
Description | Value |
---|---|
SQLTYPE | 453 |
SQLLEN | 4 |
SQLDATA(3:4) | 37 |
SQLNAME | 7 PHONENO |
Your program might need to alter the SQLN value if the SQLDA is not large enough to contain the described SQLVAR elements. For example, suppose that instead of the estimated maximum of 20 columns, the SELECT statement actually returns 27. SQL cannot describe this select-list because the SQLVAR needs more elements than the allocated space allows. Instead, SQL sets the SQLD to the actual number of columns specified by the SELECT statement and the remainder of the structure is ignored. Therefore, after a DESCRIBE, you should compare the SQLN value to the SQLD value. If the value of SQLD is greater than the value of SQLN, allocate a larger SQLDA based on the value in SQLD, as follows, and perform the DESCRIBE again:
EXEC SQL DESCRIBE S1 INTO :SQLDA; IF SQLN <= SQLD THEN DO; /*Allocate a larger SQLDA using the value of SQLD.*/ /*Reset SQLN to the larger value.*/ EXEC SQL DESCRIBE S1 INTO :SQLDA; END;
If you use DESCRIBE on a non SELECT statement, SQL sets SQLD to 0. Therefore, if your program is designed to process both SELECT and non SELECT statements, you can describe each statement after it is prepared to determine whether it is a SELECT statement. This example is designed to process only SELECT statements; the SQLD value is not checked.
Your program must now analyze the elements of SQLVAR returned from the successful DESCRIBE. The first item in the select-list is WORKDEPT. In the SQLTYPE field, the DESCRIBE returns a value for the data type of the expression and whether nulls are applicable or not.
In this example, SQL sets SQLTYPE to 453 in SQLVAR element 1. This specifies that WORKDEPT is a fixed-length character string result column and that nulls are permitted in the column.
SQL sets SQLLEN to the length of the column. Because the data type of WORKDEPT is CHAR, SQL sets SQLLEN equal to the length of the character column. For WORKDEPT, that length is 3. Therefore, when the SELECT statement is later run, a storage area large enough to hold a CHAR(3) string will be needed.
Because the data type of WORKDEPT is CHAR FOR SBCS DATA, the first 4 bytes of SQLDATA were set to the CCSID of the character column.
The last field in an SQLVAR element is a varying-length character string called SQLNAME. The first 2 bytes of SQLNAME contain the length of the character data. The character data itself is typically the name of a column used in the SELECT statement, in this case WORKDEPT. The exceptions to this are select-list items that are unnamed, such as functions (for example, SUM(SALARY)), expressions (for example, A+B-C), and constants. In these cases, SQLNAME is an empty string. SQLNAME can also contain a label rather than a name. One of the parameters associated with the PREPARE and DESCRIBE statements is the USING clause. You can specify it this way:
EXEC SQL DESCRIBE S1 INTO:SQLDA USING LABELS;
If you specify:
In this example, the second SQLVAR element contains the information for the second column used in the select: PHONENO. The 453 code in SQLTYPE specifies that PHONENO is a CHAR column. SQLLEN is set to 4.
Now you need to set up to use the SQLDA to retrieve values when running the SELECT statement.
After analyzing the result of the DESCRIBE, you can allocate storage for variables that are to contain the result of the SELECT statement. For WORKDEPT, a character field of length 3 must be allocated; for PHONENO, a character field of length 4 must be allocated. Since both of these results can be the NULL value, an indicator variable must be allocated for each field as well.
After the storage is allocated, you must set SQLDATA and SQLIND to point to the allocated storage areas. For each element of the SQLVAR array, SQLDATA points to the place where the result value is to be put. SQLIND points to the place where the null indicator value is to be put. The following tables show what the structure looks like now. Only the entries that are meaningful in this context are shown:
Description | Value |
---|---|
SQLAID | 'SQLDA' |
SQLDABC | 1616 |
SQLN | 20 |
SQLD | 2 |
Description | Value |
---|---|
SQLTYPE | 453 |
SQLLEN | 3 |
SQLDATA | Pointer to area for CHAR(3) result |
SQLIND | Pointer to 2 byte integer indicator for result column |
Description | Value |
---|---|
SQLTYPE | 453 |
SQLLEN | 4 |
SQLDATA | Pointer to area for CHAR(4) result |
SQLIND | Pointer to 2 byte integer indicator for result column |
You are now ready to retrieve the SELECT statements results. Dynamically defined SELECT statements must not have an INTO statement. Therefore, all dynamically defined SELECT statements must use a cursor. Special forms of the DECLARE, OPEN, and FETCH are used for dynamically defined SELECT statements.
The DECLARE statement for the example statement is:
EXEC SQL DECLARE C1 CURSOR FOR S1;
As you can see, the only difference is that the name of the prepared SELECT statement (S1) is used instead of the SELECT statement itself. The actual retrieval of result rows is made as follows:
EXEC SQL OPEN C1; EXEC SQL FETCH C1 USING DESCRIPTOR :SQLDA; DO WHILE (SQLCODE = 0); /*Process the results pointed to by SQLDATA*/ EXEC SQL FETCH C1 USING DESCRIPTOR :SQLDA; END; EXEC SQL CLOSE C1;
The cursor is opened. The result rows from the SELECT are then returned one at a time using a FETCH statement. On the FETCH statement, there is no list of output host variables. Instead, the FETCH statement tells SQL to return results into areas described by your SQLDA. The results are returned into the storage areas pointed to by the SQLDATA and SQLIND fields of the SQLVAR elements. After the FETCH statement has been processed, the SQLDATA pointer for WORKDEPT has its referenced value set to 'E11'. Its corresponding indicator value is 0 since a non-null value was returned. The SQLDATA pointer for PHONENO has its referenced value set to '4502'. Its corresponding indicator value is also 0 since a non-null value was returned.