Using Embedded SQL This chapter helps you to understand and apply the basic techniques of embedded SQL programming. Topics are: Using Host Variables Using Indicator Variables The Basic SQL Statements Using the SELECT Statement Using the INSERT Statement Using Subqueries Using the UPDATE Statement Using the DELETE Statement Using the WHERE Clause Using Cursors Using the DECLARE CURSOR Statement Using the OPEN Statement Using the FETCH Statement Using the CLOSE Statement Optimizer Hints Using the CURRENT OF Clause Using All the Cursor Statements A Complete Example A Complete Example The following complete program illustrates the use of a cursor and the FETCH statement. The program prompts for a department number, then displays the names of all employees in that department. All FETCHes except the final one return a row and, if no errors were detected during the FETCH, a success status code. The final FETCH fails and returns the "no data found" Oracle error code to sqlca.sqlcode. The cumulative number of rows actually FETCHed is found in sqlerrd[2] in the SQLCA. #include <stdio.h> /* declare host variables */ char userid[12] = "SCOTT/TIGER"; char emp_name[10]; int emp_number; int dept_number; char temp[32]; void sql_error(); /* include the SQL Communications Area */ #include <sqlca.h> 1 main() { emp_number = 7499; /* handle errors */ EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); /* connect to Oracle */ EXEC SQL CONNECT :userid; printf("Connected.\n"); /* declare a cursor */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename FROM emp WHERE deptno = :dept_number; printf("Department number? "); gets(temp); dept_number = atoi(temp); /* open the cursor and identify the active set */ EXEC SQL OPEN emp_cursor; printf("Employee Name\n"); printf("-------------\n"); /* fetch and process data in a loop exit when no more data */ EXEC SQL WHENEVER NOT FOUND DO break; while (1) { EXEC SQL FETCH emp_cursor INTO :emp_name; printf("%s\n", emp_name); } EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; exit(0); } void sql_error(msg) char *msg; { char buf[500]; int buflen, msglen; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; buflen = sizeof (buf); sqlglm(buf, &buflen, &msglen); printf("%s\n", msg); printf("%*.s\n", msglen, buf); exit(1); } 2 Using Embedded PL/SQL This chapter shows you how to improve performance by embedding PL/SQL transaction processing blocks in your program. After pointing out the advantages of PL/SQL, this chapter discusses the following subjects: Embedding PL/SQL Blocks Using Host Variables Using Indicator Variables Using Host Arrays Using Cursors Stored Subprograms Using Dynamic SQL Embedding PL/SQL Blocks The Pro*C/C++ Precompiler treats a PL/SQL block like a single embedded SQL statement. So, you can place a PL/SQL block anywhere in a program that you can place a SQL statement. To embed a PL/SQL block in your Pro*C/C++ program, simply bracket the PL/SQL block with the keywords EXEC SQL EXECUTE and END-EXEC as follows: EXEC SQL EXECUTE DECLARE ... BEGIN ... END; END-EXEC; The keyword END-EXEC must be followed by a semicolon. After writing your program, you precompile the source file in the usual way. When the program contains embedded PL/SQL, you must use the SQLCHECK=SEMANTICS command-line option, since the PL/SQL must be parsed by the Oracle Server. SQLCHECK=SEMANTICS requires the USERID option also, to connect to a server. For more information, see "Using the Precompiler Options" on page 9-10. Using Host Variables Host variables are the key to communication between a host language and a PL/SQL block. Host variables can be shared with PL/SQL, meaning that PL/SQL can set and reference host variables. For example, you can prompt a user for information and use host variables to pass that information to a PL/SQL block. Then, PL/SQL can access the database and use host variables to pass the results back to your host program. 3 Inside a PL/SQL block, host variables are treated as global to the entire block and can be used anywhere a PL/SQL variable is allowed. Like host variables in a SQL statement, host variables in a PL/SQL block must be prefixed with a colon. The colon sets host variables apart from PL/SQL variables and database objects. Restrictions on Host Variables You can not use complex C expressions such as structure-member dereferencing in PL/SQL blocks. For more details and examples, see "Restriction" on page 6-12. An Example The following example illustrates the use of host variables with PL/SQL. The program prompts the user for an employee number, then displays the job title, hire date, and salary of that employee. char username[100], password[20]; char job_title[20], hire_date[9], temp[32]; int emp_number; float salary; #include <sqlca.h> printf("Username? \n"); gets(username); printf("Password? \n"); gets(password); EXEC SQL WHENEVER SQLERROR GOTO sql_error; EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("Connected to Oracle\n"); for (;;) { printf("Employee Number (0 to end)? "); gets(temp); emp_number = atoi(temp); if (emp_number == 0) { EXEC SQL COMMIT WORK RELEASE; printf("Exiting program\n"); break; } /*-------------- begin PL/SQL block -----------------*/ EXEC SQL EXECUTE BEGIN SELECT job, hiredate, sal INTO :job_title, :hire_date, :salary FROM emp WHERE empno = :emp_number; END; END-EXEC; /*-------------- end PL/SQL block -----------------*/ printf("Number Job Title Hire Date 4 Salary\n"); printf("------------------------------------\n"); printf("%6d %8.8s %9.9s %6.2f\n", emp_number, job_title, hire_date, salary); } ... exit(0); sql_error: EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; printf("Processing error\n"); exit(1); Notice that the host variable emp_number is set before the PL/SQL block is entered, and the host variables job_title, hire_date, and salary are set inside the block. VARCHAR Pseudotype Recall from Chapter 3 that you can use the VARCHAR datatype to declare variable-length character strings. If the VARCHAR is an input host variable, you must tell Oracle what length to expect. So, set the length component to the actual length of the value stored in the string component. If the VARCHAR is an output host variable, Oracle automatically sets the length component. However, to use a VARCHAR output host variable in your PL/SQL block, you must initialize the length component before entering the block. So, set the length component to the declared (maximum) length of the VARCHAR, as shown here: int emp_number; varchar emp_name[10]; float salary; ... emp_name.len = 10; /* initialize length component */ EXEC SQL EXECUTE BEGIN SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE empno = :emp_number; ... END; END-EXEC; ... Restriction Do not use C pointer or array syntax in PL/SQL blocks. The PL/SQL compiler does not understand C host-variable expressions and is, therefore, unable to parse them. For example, the following is invalid: EXEC SQL EXECUTE BEGIN :x[5].name := 'SCOTT'; ... END; 5 END-EXEC; To avoid syntax errors, use a placeholder (a temporary variable), to hold the address of the structure field to populate structures as shown in the following valid example: name = &employee.name EXEC SQL EXECUTE BEGIN :name := ...; ... END; END-EXEC; However, a host language such as C needs indicator variables because it cannot manipulate nulls. Embedded PL/SQL meets this need by letting you use indicator variables to accept nulls input from a host program output nulls or truncated values to a host program When used in a PL/SQL block, indicator variables are subject to the following rules: You cannot refer to an indicator variable by itself; it must be appended to its associated host variable. If you refer to a host variable with its indicator variable, you must always refer to it that way in the same block. In the following example, the indicator variable ind_comm appears with its host variable commission in the SELECT statement, so it must appear that way in the IF statement: ... EXEC SQL EXECUTE BEGIN SELECT ename, comm INTO :emp_name, :commission :ind_comm FROM emp WHERE empno = :emp_number; IF :commission :ind_comm IS NULL THEN ... ... END; END-EXEC; Notice that PL/SQL treats :commission :ind_comm like any other simple variable. Though you cannot refer directly to an indicator variable inside a PL/SQL block, PL/SQL checks the value of the indicator variable when entering the block and sets the value correctly when exiting the block. Stored Subprograms Unlike anonymous blocks, PL/SQL subprograms (procedures and functions) can be compiled separately, stored in an Oracle database, and invoked. A subprogram explicitly CREATEd using an Oracle tool such as SQL*Plus or SQL*DBA is called a stored subprogram. Once compiled and stored in the data dictionary, it is a database object, which can be re-executed without being recompiled. When a subprogram within a PL/SQL block or stored procedure is sent to Oracle by your application, it is called an inline subprogram. Oracle compiles the inline subprogram and caches it in the System Global Area (SGA) but does not store the source or object code in the data dictionary. 6 Subprograms defined within a package are considered part of the package, and so are called packaged subprograms. Stored subprograms not defined within a package are called stand-alone subprograms. Creating Stored Subprograms You can embed the SQL statements CREATE FUNCTION, CREATE PROCEDURE, and CREATE PACKAGE in a host program, as the following example shows: EXEC SQL CREATE FUNCTION sal_ok (salary REAL, title CHAR) RETURN BOOLEAN AS min_sal REAL; max_sal REAL; BEGIN SELECT losal, hisal INTO min_sal, max_sal FROM sals WHERE job = title; RETURN (salary >= min_sal) AND (salary <= max_sal); END sal_ok; END-EXEC; Notice that the embedded CREATE {FUNCTION | PROCEDURE | PACKAGE} statement is a hybrid. Like all other embedded CREATE statements, it begins with the keywords EXEC SQL (not EXEC SQL EXECUTE). But, unlike other embedded CREATE statements, it ends with the PL/SQL terminator END-EXEC. In the example below, you create a package that contains a procedure named get_employees, which fetches a batch of rows from the EMP table. The batch size is determined by the caller of the procedure, which might be another stored subprogram or a client application. The procedure declares three PL/SQL tables as OUT formal parameters, then fetches a batch of employee data into the PL/SQL tables. The matching actual parameters are host arrays. When the procedure finishes, it automatically assigns all row values in the PL/SQL tables to corresponding elements in the host arrays. EXEC SQL CREATE OR REPLACE PACKAGE emp_actions AS TYPE CharArrayTyp IS TABLE OF VARCHAR2(10) INDEX BY BINARY_INTEGER; TYPE NumArrayTyp IS TABLE OF FLOAT INDEX BY BINARY_INTEGER; PROCEDURE get_employees( dept_number IN INTEGER, batch_size IN INTEGER, found IN OUT INTEGER, done_fetch OUT INTEGER, emp_name OUT CharArrayTyp, job-title OUT CharArrayTyp, salary OUT NumArrayTyp); END emp_actions; END-EXEC; EXEC SQL CREATE OR REPLACE PACKAGE BODY emp_actions AS CURSOR get_emp (dept_number IN INTEGER) IS SELECT ename, job, sal FROM emp WHERE deptno = dept_number; 7 PROCEDURE get_employees( dept_number IN INTEGER, batch_size IN INTEGER, found IN OUT INTEGER, done_fetch OUT INTEGER, emp_name OUT CharArrayTyp, job_title OUT CharArrayTyp, salary OUT NumArrayTyp) IS BEGIN IF NOT get_emp%ISOPEN THEN OPEN get_emp(dept_number); END IF; done_fetch := 0; found := 0; FOR i IN 1..batch_size LOOP FETCH get_emp INTO emp_name(i), job_title(i), salary(i); IF get_emp%NOTFOUND THEN CLOSE get_emp; done_fetch := 1; EXIT; ELSE found := found + 1; END IF; END LOOP; END get_employees; END emp_actions; END-EXEC; You specify the REPLACE clause in the CREATE statement to redefine an existing package without having to drop the package, recreate it, and regrant privileges on it. For the full syntax of the CREATE statement see Oracle8 SQL Reference. If an embedded CREATE {FUNCTION | PROCEDURE | PACKAGE} statement fails, Oracle generates a warning, not an error. Calling a Stored Subprogram To invoke (call) a stored subprogram from your host program, you must use an anonymous PL/SQL block. In the following example, you call a stand-alone procedure named raise_salary: EXEC SQL EXECUTE BEGIN raise_salary(:emp_id, :increase); END; END-EXEC; Notice that stored subprograms can take parameters. In this example, the actual parameters emp_id and increase are C host variables. In the next example, the procedure raise_salary is stored in a package named emp_actions, so you must use dot notation to fully qualify the procedure call: 8 EXEC SQL EXECUTE BEGIN emp_actions.raise_salary(:emp_id, :increase); END; END-EXEC; An actual IN parameter can be a literal, scalar host variable, host array, PL/SQL constant or variable, PL/SQL table, PL/SQL user-defined record, procedure call, or expression. However, an actual OUT parameter cannot be a literal, procedure call, or expression. In the following example, three of the formal parameters are PL/SQL tables, and the corresponding actual parameters are host arrays. The program calls the stored procedure get_employees (see page 6-21) repeatedly, displaying each batch of employee data, until no more data is found. This program is available on-line in the demo directory, in the file sample9.pc. A SQL script to create the CALLDEMO stored package is available in the file calldemo.sql. /************************************************************* Sample Program 9: Calling a stored procedure This program connects to ORACLE using the SCOTT/TIGER account. The program declares several host arrays, then calls a PL/SQL stored procedure (GET_EMPLOYEES in the CALLDEMO package) that fills the table OUT parameters. The PL/SQL procedure returns up to ASIZE values. Sample9 keeps calling GET_EMPLOYEES, getting ASIZE arrays each time, and printing the values, until all rows have been retrieved. GET_EMPLOYEES sets the done_flag to indicate "no more data." *************************************************************/ #include <stdio.h> #include <string.h> EXEC SQL INCLUDE sqlca.h; typedef char asciz[20]; typedef char vc2_arr[11]; EXEC SQL BEGIN DECLARE SECTION; /* User-defined type for null-terminated strings */ EXEC SQL TYPE asciz IS STRING(20) REFERENCE; /* User-defined type for a VARCHAR array element. */ EXEC SQL TYPE vc2_arr IS VARCHAR2(11) REFERENCE; asciz username; asciz password; int dept_no; vc2_arr emp_name[10]; vc2_arr job[10]; float salary[10]; int done_flag; int array_size; int num_ret; EXEC SQL END DECLARE SECTION; /* which department to query? */ /* array of returned names */ /* number of rows returned */ 9 long SQLCODE; void print_rows(); void sql_error(); main() { int char /* produces program output */ /* handles unrecoverable errors */ i; temp_buf[32]; /* Connect to ORACLE. */ EXEC SQL WHENEVER SQLERROR DO sql_error(); strcpy(username, "scott"); strcpy(password, "tiger"); EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user: %s\n\n", username); printf("Enter department number: "); gets(temp_buf); dept_no = atoi(temp_buf);/* Print column headers. */ printf("\n\n"); printf("%-10.10s%-10.10s%s\n", "Employee", "Job", "Salary"); printf("%-10.10s%-10.10s%s\n", "--------", "---", "------"); /* Set the array size. */ array_size = 10; done_flag = 0; num_ret = 0; /* * * * */ Array fetch loop. The loop continues until the OUT parameter done_flag is set. Pass in the department number, and the array size-get names, jobs, and salaries back. for (;;) { EXEC SQL EXECUTE BEGIN calldemo.get_employees (:dept_no, :array_size, :num_ret, :done_flag, :emp_name, :job, :salary); END; END-EXEC; print_rows(num_ret); if (done_flag) break; } /* Disconnect from the database. */ EXEC SQL COMMIT WORK RELEASE; 10 exit(0); } void print_rows(n) int n; { int i; if (n == 0) { printf("No rows retrieved.\n"); return; } for (i = 0; i < n; i++) printf("%10.10s%10.10s%6.2f\n", emp_name[i], job[i], salary[i]); } /* Handle errors. Exit on any error. */ void sql_error() { char msg[512]; int buf_len, msg_len; EXEC SQL WHENEVER SQLERROR CONTINUE; buf_len = sizeof(msg); sqlglm(msg, &buf_len, &msg_len); printf("\nORACLE error detected:"); printf("\n%.*s \n", msg_len, msg); EXEC SQL ROLLBACK WORK RELEASE; exit(1); } Remember, the datatype of each actual parameter must be convertible to the datatype of its corresponding formal parameter. Also, before a stored procedure is exited, all OUT formal parameters must be assigned values. Otherwise, the values of corresponding actual parameters are indeterminate. Remote Access PL/SQL lets you access remote databases via database links. Typically, database links are established by your DBA and stored in the Oracle data dictionary. A database link tells Oracle where the remote database is located, the path to it, and what Oracle username and password to use. In the following example, you use the database link dallas to call the raise_salary procedure: EXEC SQL EXECUTE BEGIN raise_salary@dallas(:emp_id, :increase); END; 11 END-EXEC; You can create synonyms to provide location transparency for remote subprograms, as the following example shows: CREATE PUBLIC SYNONYM raise_salary FOR raise_salary@dallas; Getting Information about Stored Subprograms Chapter 3 described how to embed OCI calls in your host program. After calling the library routine SQLLDA to set up the LDA, use the OCI call odessp to get useful information about a stored subprogram. When you call odessp, you must pass it a valid LDA and the name of the subprogram. For packaged subprograms, you must also pass the name of the package. odessp returns information about each subprogram parameter such as its datatype, size, position, and so on. For details, see Programmer's Guide to the Oracle Call Interface. You can also use the DESCRIBE_PROCEDURE stored procedure, in the DBMS_DESCRIBE package. See Oracle8 Application Developer's Guide for more information about this procedure. Using Dynamic SQL Recall that the precompiler treats an entire PL/SQL block like a single SQL statement. Therefore, you can store a PL/SQL block in a string host variable. Then, if the block contains no host variables, you can use dynamic SQL Method 1 to EXECUTE the PL/SQL string. Or, if the block contains a known number of host variables, you can use dynamic SQL Method 2 to PREPARE and EXECUTE the PL/SQL string. If the block contains an unknown number of host variables, you must use dynamic SQL Method 4. For more information, refer to Chapter 13, "Using Dynamic SQL", and Chapter 14, "Using Dynamic SQL: Advanced Concepts". Warning: In dynamic SQL Method 4, you cannot bind a host array to a PL/SQL procedure with a parameter of type "table." For more information, see "Using Method 4" on page 13-25. 12