Skip to content

10. Dynamic SQL Statements#

Static versus Dynamic SQL Statements#

Static SQL Statements#

Concept#

Static SQL statements are those that are defined by the programmer in advance, and are unchangeable.

Static SQL statements are embedded SQL statements that are hard-coded into an application.

For more information about static SQL statements, please refer to Chapter 6: Embedded SQL Statements and Chapter8: Using Cursors.

Disadvantages#
  • Static SQL statements cannot be used in applications in which the exact form of the SQL statement cannot be determined in advance.
  • The names of tables and columns to be referred to in static SQL statements must be determined by the programmer in advance. In other words, host variables cannot be used in place of table or column names and they cannot be changed.
  • Although the use of input host variables with static SQL statements affords a small amount of flexibility, static SQL statements are fundamentally fixed, and cannot be changed in any major way.

Dynamic SQL Statements#

Concept#

The text of dynamic SQL statements is composed in an area of memory (i.e. a character-type variable) set aside for that purpose at run time. Therefore, the SQL statement does not need to be hard-coded in the application source code.

Advantages#
  • The SQL statement to be executed does not need to be predetermined within the program in advance. That is, it becomes possible to use dynamic SQL statements.
  • The tables and columns to be accessed can be determined dynamically at run time.
Disadvantage#
  • Because the SQL statement to be executed, as well as the names of the tables and columns to be accessed, are determined only at run time, dynamic SQL statements may be less effective than static SQL statements from the aspect of performance.

Using Dynamic SQL Statements#

Altibase supports for the use of four dynamic SQL statements. The built-in SQL statements should be used in the order described in each method when implementing application programs with the dynamic SQL statements as demonstrated in the following section. The four methods are explained below:

Method 1#

This method consists of only one statement: the EXECUTE IMMEDIATE statement.

This method is not suggested for use with frequently executed SQL statements, as it is inefficient and compromises performance.

This method is useful when executing DDL statements for which the entire SQL string is not known until run time.

This method cannot be used to execute SELECT statements.

Syntax#

EXEC SQL EXECUTE IMMEDIATE <:host_var | string_literal>;

Arguments#

  • <:host_var>
    This is a character-type variable that includes all of the SQL statement text.
  • <string_literal>
    This is the entire SQL statement, hard-coded in the form of a string.

Description#

When using a host variable, only one host variable can be used. This host variable must include all of the SQL statement text. Furthermore, this SQL statement text must not contain any host variables or the question mark ("?") parameter marker.

Examples#

[Example 1] The following example shows the use of Method 1 to execute a dynamic SQL statement using a provided SQL statement string.

< Sample Program: dynamic1.sc >

EXEC SQL EXECUTE IMMEDIATE DROP TABLE T1;

EXEC SQL EXECUTE IMMEDIATE CREATE TABLE T1 (I1 INTEGER, I2 INTEGER);

[Example 2] The following example shows the use of Method 1 to execute a dynamic SQL statement that is stored in a host variable

< Sample Program: dynamic1.sc >

char query[100];
strcpy(query, "drop table t2");
EXEC SQL EXECUTE IMMEDIATE :query;

strcpy(query, "create table t2(i1 integer)");
EXEC SQL EXECUTE IMMEDIATE :query;

Method 2#

Method 2 consists of two steps: the PREPARE statement and the EXECUTE statement. This method is useful in situations where a SQL statement is prepared once and then executed several times.

The SQL statement string to be used in the PREPARE statement can include the question mark ("?") parameter marker. This parameter marker will be replaced with the value of a host variable in the EXECUTE statement.

The EXECUTE statement will always use the SQL statement in its most recently prepared form, that is, in the form that was prepared by the most recently executed PREPARE statement. This means that any changes to the contents of the prepared SQL statement or to any bound variables since the most recent execution of the PREPARE statement will be ignored by the EXECUTE statement unless the PREPARE statement is executed again.

This method is not efficient in situations in which the text of the SQL statement change frequently, because the need to repeatedly execute the PREPARE and EXECUTE statements will have a negative effect on performance.

This method is useful when executing INSERT, UPDATE, and DELETE statements for which the contents of the SQL statement are determined at run time.

This method cannot be used to execute SELECT statements.

Note: Although not required with Method 2, the DECLARE STATEMENT can be provided before the PREPARE statement. Doing so will not cause an error.

PREPARE#

Syntax#

EXEC SQL PREPARE <statement_name> FROM 
<:host_var | string_literal>;

Arguments#

  • <statement_name>
    This is the identifier of the SQL statement. It must start with an alphabetic character (a ~ z, A ~ Z), the underscore character ("_"), or a dollar sign ("$"), and must not be longer than 50 bytes.
  • <:host_var>
    This is a character-type variable that includes all of the SQL statement text.
  • <string_literal>
    This is the entire SQL statement, hard-coded in the form of a string.

Description#

This statement is used to prepare a SQL statement for execution.

If the same SQL statement identifier is used in multiple PREPARE statements within the same application, then the SQL statement that is executed when the EXECUTE statement is called at run time will be the most recently prepared SQL statement having that identifier.

The SQL statement cannot be a SELECT statement.

Example#

In the following example, the text of a SQL statement is determined according to the conditions at run time, and the corresponding SQL statement is then prepared.

< Sample Program : dynamic2.sc >

char query[100];

EXEC SQL BEGIN DECLARE SECTION;
int  s_eno;
EXEC SQL END DECLARE SECTION;

if (s_eno < 20)
{
    strcpy(query, "delete from employees where eno = ? and e_lastname = ?");
}
else
{
    strcpy(query, "insert into employees(eno, e_lastname) values (?, ?)");
}

EXEC SQL PREPARE S FROM :query;

EXECUTE#

Syntax#

EXEC SQL EXECUTE <statement_name> 
[ USING <host_var_list> ];

Arguments#

  • <statement_name>
    This is the identifier of the SQL statement.
  • <host_var_list>
    This is a list of input host variables and input indicator variables.

Description#

This statement can only be executed after a corresponding PREPARE statement.

If an undefined SQL statement identifier is specified when this statement is executed, an error indicating that the SQL statement does not exist will be raised.

The identifier that is specified when this statement is executed cannot be the identifier of a SELECT statement.

The EXECUTE statement is used to execute a previously prepared SQL statement.

When this statement is executed, each parameter marker ("?") is replaced with a corresponding value. These values are specified as a list of host variables in the USING clause. The number of host variables in the USING clause must be the same as the number of parameter markers in the SQL statement. Furthermore, the type of each host variable must be compatible with the type of the database column to which it corresponds.

If the same SQL statement identifier is used in multiple PREPARE statements in the same application, then when the EXECUTE statement is called at run time, the most recently prepared SQL statement having that identifier will be executed.

Example#

The following example shows the use of the EXECUTE statement.

< Sample Program : dynamic2.sc >

EXEC SQL BEGIN DECLARE SECTION;
int  s_eno;
char s_ename[20+1];
EXEC SQL END DECLARE SECTION;
s_eno = 10;
strcpy(s_ename, "YHBAE");

EXEC SQL EXECUTE S USING :s_eno, :s_ename;

Method 3#

Method 3 is the only way to use dynamic SQL to execute a SELECT statement, and cannot be used to execute any other kind of statement.

Method 3 consists of the execution of five statements: the PREPARE statement, the DECLARE CURSOR statement, the OPEN statement, the FETCH statement, and the CLOSE statement.

It is possible to rearrange the order of the first two statements so that the DECLARE CURSOR statement precedes the PREPARE statement. In this case, it is necessary to additionally provide the DECLARE STATEMENT statement before the DECLARE CURSOR statement.

DECLARE STATEMENT#

Syntax#

EXEC SQL DECLARE <statement_name> STATEMENT;

Argument#

  • <statement_name>
    This is the identifier of the SQL statement. It must start with an alphabetic character (a ~ z, A ~ Z), the underscore character ("_"), or a dollar sign ("$"), and must not be longer than 50 bytes.

Description#

This statement is used to declare an identifier for a SQL statement to be used in other embedded SQL statements.

This statement is only required if the DECLARE CURSOR statement precedes the PREPARE statement; it is not required if the PREPARE statement precedes the DECLARE CURSOR statement.

Example#

In the following example, the DECLARE CURSOR statement precedes the PREPARE statement, and thus the DECLARE STATEMENT is required before the DECLARE CURSOR statement.

EXEC SQL DECLARE sql_stmt STATEMENT; 
EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; 
EXEC SQL PREPARE sql_stmt FROM :dyn_string;

PREPARE#

Syntax#

EXEC SQL PREPARE <statement_name> FROM 
<:host_var | string_literal>;

Argument#

  • <statement_name>
    This is the identifier of the SQL statement. It can be a maximum of 50 characters.
  • <:host_var>
    This is a character-type variable that includes all of the SQL statement text.
  • <string_literal>
    This is the entire SQL statement, hard-coded in the form of a string.

Description#

This statement is used to prepare a SQL statement for execution.

If the same SQL statement identifier is used in multiple PREPARE statements within the same application, then the SQL statement that is used when the DECLARE CURSOR statement is called at run time will be the most recently prepared SQL statement having that identifier.

The SQL statement must be a SELECT statement.

Example#

In the following example, the text of a SQL statement is determined according to the conditions at run time, and the corresponding SQL statement is then prepared.

< Sample Program : dynamic3.sc >

EXEC SQL BEGIN DECLARE SECTION;
char query[100];
EXEC SQL END DECLARE SECTION;

int  type;

switch (type)
{
case 1:
    strcpy(query, "select * from departments");
    break;
case 2:
    strcpy(query, "select * from goods");
    break;
case 3:
    strcpy(query, "select * from orders");
    break;
}

EXEC SQL PREPARE S FROM :query;

DECLARE CURSOR#

Syntax#

EXEC SQL DECLARE <cursor_name> CURSOR FOR 
<statement_name>;

Arguments#

  • <cursor_name>
    This is the name of the cursor.
  • <statement_name>
    This is the identifier of the SQL statement.

Description#

Declare a cursor with the specified SQL statement identifier.

This statement can be executed after a PREPARE statement, a CLOSE statement, or a CLOSE RELEASE statement. If an undefined SQL statement identifier is specified when the DECLARE CURSOR statement is executed, an error indicating that the SQL statement identifier does not exist will be raised.

The SQL statement identified by the specified SQL statement identifier must be a SELECT statement.

This statement declares a cursor that references the provided SQL statement identifier.

If the same SQL statement identifier is used in multiple PREPARE statements within the same application, then the cursor that is declared will reference the most recently prepared SQL statement having that identifier at run time.

Example#

In the following example, the cursor CUR, which references the SQL statement identified by the identifier S, is declared.

< Sample Program: dynamic3.sc >

EXEC SQL DECLARE CUR CURSOR FOR S;

OPEN#

Syntax#

EXEC SQL OPEN <cursor_name> [ USING <host_var_list> ];

Arguments#

  • <cursor_name>
    This is the name of the cursor.
  • <host_var_list>
    This is a list of output host variables and output indicator variables.

Description#

The OPEN statement can be executed after the DECLARE CURSOR statement or the CLOSE statement. If an attempt is made to open a cursor that has not been defined, an error indicating that the cursor does not exist will be raised.

The OPEN statement executes the SQL statement that was declared using the DECLARE CURSOR statement.

When this statement is executed, each parameter marker ("?") is replaced with a corresponding value. These values are specified as a list of host variables in the USING clause. The number of host variables in the USING clause must be the same as the number of parameter markers in the SQL statement. Furthermore, the type of each host variable must be compatible with the type of the database column to which it corresponds.

If the same cursor identifier is used in multiple DECLARE CURSOR statements within the same application, then the most recently declared cursor having that identifier at run time will be used.

Limitation#

The limitations of the SELECT statement apply.

Example#

The following example shows how to open a cursor called CUR.

< Sample Program : dynamic3.sc >

EXEC SQL OPEN CUR;

FETCH#

Syntax#

EXEC SQL FETCH <cursor_name> INTO <host_var_list>;

Arguments#

  • <cursor_name>
    This is the name of the cursor.
  • <host_var_list>
    This is a list of output host variables and output indicator variables.

Description#

The FETCH statement can be executed after the OPEN statement. If an attempt is made to perform a FETCH operation using a cursor that has not been defined, an error indicating that operations are being performed out of sequence will be raised.

This statement is used to return records that were retrieved when the cursor was opened. The host variables into which the records are returned are specified in the host variable list in the INTO clause. Unless a structure-type output host variable is being used, the number of host variables in the INTO clause must be the same as the number of columns in the SELECT clause. Furthermore, the type of each host variable must be compatible with the type of the database column to which it corresponds.

If the same cursor identifier is used in multiple DECLARE CURSOR statements within the same application, then the most recently declared cursor having that identifier at run time will be used.

Example#

The following example shows the use of the cursor CUR to fetch results into different output host variables depending on the situation. Because an indicator variable has been defined for each host variable, it is possible to handle NULL values. The use of the while loop ensures that the FETCH operation is repeated until SQL_NO_DATA is returned.

< Sample Program : dynamic3.sc >

int  type;

/* declare host variables */
EXEC SQL BEGIN DECLARE SECTION;
/*declare output host variables */
department s_department;
goods      s_goods;
orders     s_orders;        

/*declare indicator variables */
dept_ind  s_dept_ind;
good_ind  s_good_ind;
order_ind s_order_ind;
EXEC SQL END DECLARE SECTION;

while(1)
{
    /* use indicator variables to check null value */
    switch (type)
    {
    case 1:
        EXEC SQL FETCH CUR 
INTO :s_department :s_dept_ind;
        break;
    case 2:
        EXEC SQL FETCH CUR 
INTO :s_goods :s_good_ind;
        break;
    case 3:
        EXEC SQL FETCH CUR 
INTO :s_orders :s_order_ind;
        break;
    }

    if (sqlca.sqlcode == SQL_SUCCESS) 
{
        cnt++;
    }
    else if (sqlca.sqlcode == SQL_NO_DATA)
    {
        printf("%d rows selected\n\n", cnt);
        break;
    }
    else
    {
        printf("Error : [%d] %s\n\n", 
SQLCODE, sqlca.sqlerrm.sqlerrmc);
        break;
    }
}

CLOSE#

Syntax#

EXEC SQL CLOSE <cursor_name>;

Argument#

  • <cursor_name>
    This is the name of the cursor to close.

Description#

The CLOSE statement can be executed after the DECLARE CURSOR statement, the OPEN statement, or the FETCH statement. If an attempt is made to close a cursor that has not been defined, an error indicating that the cursor does not exist will be raised. If the CLOSE statement is executed when there are still data left to return on the database server (i.e. when not all of the records have been fetched), then the unfetched results will be discarded. However, the resources allocated to the cursor are not freed at this time. Therefore, after the CLOSE statement is executed, the OPEN statement can be executed immediately, without first executing the DECLARE CURSOR statement.

Example#

The following example shows the use of the CLOSE statement:

< Sample Program : dynamic3.sc >

EXEC SQL CLOSE CUR;

CLOSE RELEASE#

Syntax#

EXEC SQL CLOSE RELEASE <cursor_name>;

Argument#

  • <curso_name>
    This is the name of the cursor.

Description#

The CLOSE RELEASE statement can be executed after the DECLARE CURSOR statement, the OPEN statement, the FETCH statement, or the CLOSE statement. If an attempt is made to reference a cursor that has not been defined in the CLOSE RELEASE statement, an error indicating that the cursor does not exist will be raised.

If there are still data left to return on the database server when the CLOSE RELEASE statement is executed, then the unfetched results will be discarded. Additionally, all resources allocated to the cursor will be freed. Therefore, after the CLOSE RELEASE statement has been executed, it will be necessary to execute the DECLARE CURSOR and OPEN statements in sequence. In other words, the OPEN statement cannot be executed after the CLOSE RELEASE statement.

If it is desired to reuse a cursor, execute the CLOSE statement; if not, execute the CLOSE RELEASE statement. In practice, it is not common to use a cursor only once, so the CLOSE RELEASE statement is almost never used. Reusing a cursor name by repeatedly executing the CLOSE RELEASE, DECLARE CURSOR and OPEN statements has a negative impact on performance.

If more than one same cursor name is declared, it refers to the most recently declared cursor at runtime.

Example#

In the following example, the cursor named CUR is closed, and all resources allocated to the cursor are freed.

EXEC SQL CLOSE RELEASE CUR;

Method 4#

Method 4 supports the following methods; method 2 is comprised of PREPARE and EXECUTE statements, method 3 is comprised of PREPARE, DECLARE CURSOR, OPEN, TETCH, CLOSE, and they are supported in the method 4 in Altibase.

The method 2 and method 3 specifies parameter markers which will be used in a SQL string at the compiling phase, whereas the method 4 specifies the parameter markers during program execution.

SQLDA Structure#

The SQLDA is a structure which stores information of variables that are bound to the parameter marker. Refer to Appretix C for in-depth description of the parameter variables of the structure.

struct SQLDA
{
int       N;      /* the number of columns executing commands */
char    **V;      /* the data address */
int      *L;      /* the length of each data buffer */
short    *T;      /* each data type */ 
short   **I;      /* an indicator address */
int        F;     /* the number of analyzed columns */
}
  • SQLSQLDAAlloc(int allocSize) Function
    This function allocates the memory by using SQLDA structure.
  • SQLSQLDAFree( SQLDA *sqlda ) Function
    This function releases the SQLDA structure from the memory.

The following illustrations describe the SQLDA structure using the bind variables function and select list function, respectively.

Sequential Operation Process#

The Operation Process of executing INSERT, UPDATE, DELETE statement#
  1. CONNECT: Connect to DB.
  2. Initialize SQLDA
  3. PREPARE: Prepare for input of a dynamic query.
  4. BIND VARIABLES: Obtain information of parameter markers with SQL statements.
  5. Store the parameter information and relevant data obtained by the step 4.
  6. EXECUTE: Execute a query.
The Operation Sequence of SELECT statement#
  1. CONNECT: Connect to DB.
  2. Initialize SQLDA
  3. PREPARE: Prepare for input of a dynamic query.
  4. DECLARE CURSOR: Declare a cursor.
  5. BIND VARIABLES: Obtain information of parameter markers with SQL statements.
  6. Store the information of parameter and the relevant data obtained by step 5 to the SQLDA structure.
  7. OPEN CURSOR: Open the cursor.
  8. SELECT LIST: Obtain information of the SQL query column.
  9. Store the FETCH information obtained by 8 to the SQLDA structure.
  10. FETCH: Execute a query by using the cursor.

ARRAY SIZE SET#

Syntax#

EXEC SQL FOR <statement_name> <:host_var>;

Arguments#

  • <statement_name>
    This is an identifier of a SQL statement. It should begin wtih an alphabet letter(a to z or A toZ), an underline("_") or a dollor sign ("$"), and its maximum length should be restricted within 50 bytes.
  • <:host_var>
    This is an array size which will be processed at once.

Description#

The number of arrays which will be processed is defined, and it is the array size for executing the INSERT, UPDATE, and DELETE statements.

Example#

#define ARRAY_SIZE 3
/* declare host variables */
EXEC SQL BEGIN DECLARE SECTION; 
int arrSize = ARRAY_SIZE;
EXEC SQL END DECLARE SECTION;
EXEC SQL FOR INSERT_STMT :arrSize;

BIND VARIABLES#

Syntax#

EXEC SQL DESCRIBE BIND VARIABLES FOR <statement_name> INTO <:sqlda_var>;

Arguments#

  • <statement_name>
    This is an identifier of a SQL statement. It should begin wtih an alphabet letter(a to z or A to Z), an underline("_") or a dollor sign ("$"), and its maximum length should be restricted within 50 bytes.
  • <:sqlda_var >
    This is a name of the SQLDA structure, and the type is identical with <:host_var>.

Description#

The information of parameter markers in a SQL statement used in the PREPARE statement is retrieved.

Example#

/* bind variables initialization */ 
bindInfo = (SQLDA*) SQLSQLDAAlloc( MAX_COLUMN_SIZE );
bindInfo->N = MAX_COLUMN_SIZE;
EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO :bindInfo;

CURSOR OPEN#

Syntax#

EXEC SQL OPEN <cursor_name> USING DESCRIPTOR <:sqlda_var>;

Argument#

  • <cursor_name>
    This indicates a cursor name.
  • <:sqlda_var >
    It is a variable name of the SQLDA structure, and the type is identical with <:host_var> type.

Description#

It opens a cursor.

Example#

EXEC SQL OPEN CUR USING DESCRIPTOR :bindInfo;
/* check sqlca.sqlcode */
if(sqlca.sqlcode != SQL_SUCCESS)
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
}

EXECUTE#

Syntax#

EXEC SQL EXECUTE <statement_name> USING DESCRIPTOR <:sqlda_var>;

Arguments#

  • <statement_name>
    This is an identifier of a SQL statement. It should begin wtih an alphabet letter(a to z or A toZ), an underline("_") or a dollor sign ("$"), and its maximum length should be restricted within 50 bytes.
  • <:sqlda_var >
    It is a variable name of the SQLDA structure, and the type is identical with <:host_var> type.

Description#

The EXECUTE command implements execution of a SQL statement. It can be executed after the PREPARE statement, and when executing without inserting a SQL statement, an error with a message "The statement does not exist." would be raised. The value for parameter markers must be specified in :sqlda_var.

Example#

EXEC SQL EXECUTE S USING DESCRIPTOR :bindInfo;
/* check sqlca.sqlcode */
if(sqlca.sqlcode == SQL_SUCCESS)
{
printf("\nEXECUTE SUCCESS\n\n");
}
else
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
}

FETCH#

Syntax#

EXEC SQL FETCH <cursor_name> USING DESCRIPTOR <:sqlda_var>;

Arguments#

  • <cursor_name>
    This indicates a cursor name.
  • <:sqlda_var >
    It is a variable name of the SQLDA structure, and the type is identical with <:host_var>.

Description#

The result value is obtained by moving a cursor. The execution can be implemented after opening the cursor, and the variables of :sqlda_var should be also specified. An error stating "Function sequence error" would be incurred when executing FETCH on a cursor which in not open.

Example#

while(1)
{
EXEC SQL FETCH CUR USING DESCRIPTOR :selectInfo;
if( sqlca.sqlcode == SQL_SUCCESS || sqlca.sqlcode == SQL_SUCCESS_WITH_INFO )
{
printf("\n");
for( i=0; i < selectInfo->N ; i++)
{
if( *(SQLLEN*)(selectInfo->I[i]) == -1 )
{
printf("NULL  ");
}
else
{
printf("%s  ", selectInfo->V[i]);
}
}
}
else if( sqlca.sqlcode == SQL_NO_DATA)
{
break;
}
else
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
break;
}
}

Sample Programs#

dynamic1.sc#

This sample program can be found at $ALTIBASE_HOME/sample/APRE/dynamic1.sc

Result of Execution#
$ is -f schema/schema.sql
$ make dynamic1
$ ./dynamic1
<DYNAMIC SQL METHOD 1>
------------------------------------------------------
[Using String Literal]                                            
------------------------------------------------------
Success execution with string literal

------------------------------------------------------
[Using Host Variable]                                             
------------------------------------------------------
Success execution with host variable

dynamic2.sc#

This sample program can be found at $ALTIBASE_HOME/sample/APRE/dynamic2.sc

Result of Execution#
$ is -f schema/schema.sql
$ make dynamic2
$ ./dynamic2
<DYNAMIC SQL METHOD 2>
------------------------------------------------------
[Prepare]                                                         
------------------------------------------------------
Success prepare

------------------------------------------------------
[Execute]                                                         
------------------------------------------------------
Success execute

dynamic3.sc#

This sample program can be found at $ALTIBASE_HOME/sample/APRE/dynamic3.sc

Result of Execution#
$ is -f schema/schema.sql
$ make dynamic3
$ ./dynamic3
<DYNAMIC SQL METHOD 3>
------------------------------------------------------
[Prepare]                                                         
------------------------------------------------------
Success prepare

------------------------------------------------------
[Declare Cursor]                                                  
------------------------------------------------------
Success declare cursor

------------------------------------------------------
[Open Cursor]                                                     
------------------------------------------------------
Success open cursor

------------------------------------------------------
[Fetch Cursor]                                                    
------------------------------------------------------
30 rows selected

------------------------------------------------------
[Close Cursor]                                                    
------------------------------------------------------
Success close cursor

dynamic4.sc#

This sample program can be found at $ALTIBASE_HOME/sample/APRE/dynamic4.sc

Result of Execution#
$ is -f schema/schema.sql
$ make dynamic4
$ ./dynamic4
<DYNAMIC SQL METHOD 4>
iSQL> insert into DEPARTMENTS values(?,?,?,?)
Enter value for 0 bind variable : 1
Enter value for 1 bind variable : 2
Enter value for 2 bind variable : 3
Enter value for 3 bind variable : 4
EXECUTE SUCCESS

iSQL> select * from DEPARTMENTS
1 2 3 4

iSQL> delete from DEPARTMENTS where DNO=?
Enter value for 0 bind variable : 1
EXECUTE SUCCESS 
iSQL> select * from DEPARTMENTS
iSQL> exit