781.txt ===================================================================== Using Cursors ===================================================================== PRODUCT: R:BASE VERSION: 4.5+ or Higher ===================================================================== CATALOG: Programming In R:BASE AREA : Logic & Data Manipulation ===================================================================== A cursor is a valuable programming tool. It is a pointer to rows in a table. A cursor lets you step through rows one by one, performing the same action on each row. You can set a cursor to point to all the rows in a table or to a subset of rows. A cursor is set using the DECLARE CURSOR command. The DECLARE CURSOR command does not work by itself, but is really a sequence of commands. In addition to the DECLARE CURSOR, the OPEN and FETCH commands are required. A WHILE loop is used to step through the rows and perform the programmed action on each row. The CLOSE or DROP command is used after the cursor has stepped through all the rows. The basic sequence of commands for a cursor is as follows: DECLARE c1 CURSOR FOR + SELECT custid, company FROM customer OPEN c1 FETCH c1 INTO vcustid1 ind1, vcompany ind2 WHILE SQLCODE <> 100 THEN -- Place code for row by row actions here. FETCH c1 INTO vcustid1 ind1, vcompany ind2 ENDWHILE DROP CURSOR c1 The DECLARE CURSOR command names the cursor and defines the set of rows. The cursor name is then used in the OPEN, FETCH, CLOSE, and DROP commands that reference it. A cursor name can be up to 18 characters long and follows the same naming conventions as all other names in R:BASE. More than one cursor can be defined and open at a time. SELECT is used in the DECLARE CURSOR to identify the rows to step through. The SELECT part of a cursor declaration can point to rows from a single table or from multiple tables, and can choose all or only some of the columns from a table. You can use the GROUP BY clause as well as the WHERE and ORDER BY clauses of SELECT. The OPEN command initializes the cursor and tells R:BASE you are ready to retrieve a row of data from the cursor. The OPEN command positions the cursor at the first row of the set of data defined by the SELECT in the cursor declaration. The FETCH command retrieves a row of data into the specified variables. The number of variables must match the number of columns listed in the SELECT part of the DECLARE CURSOR command. Each variable has a corresponding indicator variable, which tells if a NULL value was retrieved. The list of variable pairs - data variable and indicator variable - is separated by commas. The FETCH command sets SQLCODE, the SQL error variable. If a row was retrieved, SQLCODE is set to 0. After the last row is retrieved, FETCH sets SQLCODE to 100 - no more data. Using SQLCODE as the condition for the WHILE loop lets you easily retrieve and act on each successive row. Placing a second FETCH command immediately before the ENDWHILE command keeps fetching rows until the end of data is reached. Then the loop exits. Within the WHILE loop, place whatever commands are needed to operate on each row. You can look up additional data, perform mathematical calculations, update data, and so on. Other articles in this Exchange demonstrate actions that can be performed each row using a cursor. When the cursor completes and the WHILE loop is exited, the cursor is dropped with the DROP CURSOR command. A cursor name must be dropped before it can be declared again. DROP removes a cursor definition from memory; to use the cursor again, it must be declared with the DECLARE CURSOR command. CLOSE leaves a cursor definition in memory; to use the cursor again, it is opened with the OPEN command. After a cursor has been closed, an OPEN repositions the pointer at the first row of the cursor definition. CLOSE is most often used with nested cursors, DROP with individual cursors. When a cursor is open, you can use a special WHERE clause option, WHERE CURRENT OF cursorname. This WHERE clause works with the UPDATE, DELETE, SET VAR, and SELECT commands to perform the specified action on the row the cursor is currently pointing at. The DELETE deletes the entire row; the SET VAR, SELECT, and UPDATE only operate on columns included in the SELECT part of the DECLARE CURSOR command. Note that not every cursor definition supports use of the WHERE CURRENT OF cursorname. You don't have to use the WHERE CURRENT OF cursorname in your WHERE clause. You can use a WHERE clause that explicitly points to a row of data using values stored in variables. The unique row identifier is fetched into a variable, then that value is used to access rows in the cursor table or other tables. -- The special WHERE clause WHERE CURRENT OF -- points to the current row of the cursor. SELECT custid, company FROM customer + WHERE CURRENT OF c1 UPDATE customer SET custid = (custid + 1000) + WHERE CURRENT OF c1 DELETE FROM customer WHERE CURRENT OF c1 -- Alternatively, use an explicit WHERE -- clause to access a row. SELECT custid, company FROM customer + WHERE custid = .vcustid UPDATE customer SET custid = (custid + 1000) + WHERE custid = .vcustid DELETE FROM customer WHERE custid = .vcustid This is the basic cursor structure. Other types of cursors and cursor structures that are used are: multi-table cursors, non- updatable cursors, nested cursors, resetable cursors, and scrolling cursors. Each is briefly described below. Multi-table Cursor A multi-table cursor includes more than one table in the SELECT part of the cursor declaration. You can link tables directly in the DECLARE CURSOR command; you don't need to define a view to retrieve data from more than one table. The DECLARE CURSOR command has the full capabilities of the SELECT command to do multi-table queries. As with the SELECT command itself, you list the columns to retrieve, the tables to get the data from, then link the tables in the WHERE clause. For example, -- Select data from both the Customer -- and Transmaster tables. DECLARE C1 CURSOR FOR SELECT + custid, company, transid, transdate, invoicetotal + FROM customer, transmaster + WHERE customer.custid = transmaster.custid OPEN C1 -- The fetch retrieves all the specified columns -- into variables. FETCH C1 INTO vcustid1 ind1, vcompany ind2 + vtransid ind3, vtransdate ind4, vinvoicetotal ind5 WHILE SQLCODE <> 100 THEN -- Place code for row by row actions here. -- An explicit WHERE clause must be used, -- WHERE CURRENT OF is not supported with -- multi-table cursors. -- Get the next row FETCH C1 INTO vcustid1 ind1, vcompany ind2 + vtransid ind3, vtransdate ind4, vinvoicetotal ind5 ENDWHILE DROP CURSOR C1 Notice that the basic structure of the cursor commands doesn't change. You still declare the cursor, open it, fetch the first row, then use a WHILE loop to step through each row. There is no limit to the number of tables that can be included in a DECLARE CURSOR command. The tables are joined together in the same way they are joined with a regular SELECT command. A multi-table cursor definition is a non-updatable cursor, however. You cannot update the cursor directly by using WHERE CURRENT OF cursorname. You must use explicit WHERE clauses to access the cursor tables. Non-updatable cursors A non-updatable cursor is one that does not support use of the special WHERE clause WHERE CURRENT OF cursorname. An explicit WHERE clause must be used to access data in the tables. A non-updatable cursor is a multi-table cursor, or a cursor that is defined, for example, using the GROUP BY clause. The SELECT command that defines the cursor rows does not allow the cursor to point back to a single specific row in a table. Non-updatable cursors are a very useful part of the DECLARE CURSOR structure. Use the power of the SELECT command in the DECLARE CURSOR declaration to dramatically improve the performance of a cursor. The more work the cursor does, the less your program has to do and the faster and more efficiently it will run. When using a non-updatable cursor, make sure you fetch a unique row identifier for use in WHERE clauses. Nested cursors A nested cursor involves two DECLARE CURSOR definitions. The second cursor is dependent on the first and its cursor definition uses a variable value fetched by the first cursor. There is a specific structure recommended for nested cursors - a row is retrieved from cursor one, then the matching rows in cursor two are retrieved and stepped through. Then the next row is retrieved from cursor one and its matching rows from cursor two are stepped through. The process continues until all rows have been retrieved from cursor one. -- The DECLARE commands are done together -- at the top of the program. -- An OPEN cursor does not need to immediately -- follow the corresponding DECLARE CURSOR command SET VAR vcustid INTEGER DECLARE c1 CURSOR FOR SELECT custid, company + FROM customer ORDER BY company -- The second cursor uses a variable in the -- WHERE clause. This variable, vcustid, must be -- defined earlier in the program. -- The cursor retrieves rows for a single customer only DECLARE c2 CURSOR FOR + SELECT custid, contfname, contlname + FROM contact WHERE custid = .vcustid -- Cursor c1 is opened and the first row retrieved -- from the Customer table OPEN c1 FETCH c1 INTO vcustid1 ind1, vcompany ind2 WHILE SQLCODE <> 100 THEN -- Cursor c2 is opened, it points to all the -- rows in the Contact table that match the -- custid fetched into vcustid by cursor c1. OPEN c2 -- Get the first row from the contact table and step -- through all matching rows. FETCH c2 INTO vcustid1 ind1, vfirstname ind2, + vlastname ind3 WHILE SQLCODE <> 100 THEN -- Place code here to do row by row actions --Get the next row for cursor c2 FETCH c2 INTO vcustid1 ind1, vfirstname ind2, + vlastname ind3 ENDWHILE -- After all the matching rows in the contact table -- have been processed, close cursor c2 and get the -- next row from the Customer table. -- Cursor c2 is closed and not dropped because -- the definition will be reused for the next -- row from cursor c1. CLOSE c2 -- Get the next row for cursor c1 FETCH c1 INTO vcustid1 ind1, vcompany ind2 ENDWHILE -- Both cursors are dropped when all the rows -- in the Customer table have been retrieved. DROP CURSOR c2 DROP CURSOR c1 You can use the same WHILE loop condition, SQLCODE <> 100, for both cursors. This works very well and there is no conflict between the two loops. The relative FETCH command sets the value of SQLCODE. Notice that the FETCH from cursor c2 is right before the ENDWHILE of the inner WHILE loop ensuring that that FETCH command is the one being tested by the WHILE loop. The FETCH from cursor c1 is right before the ENDWHILE of the outer WHILE loop, which then continues based on cursor c1. This placement of the DECLARE, OPEN, FETCH, WHILE, and ENDWHILE statements will always work. Just make sure the ENDWHILE is the next command after the FETCH. With nested cursors, the inner cursor is closed and opened so that it always references the matching rows from the outer cursor. An alternative to opening and closing the inner cursor is to use the RESET option on the OPEN command. Resetable cursors A DECLARE CURSOR can use a variable in its WHERE clause. Each time the cursor is opened, the WHERE clause is reevaluated using the current variable value and identifies a new set of data. You can CLOSE and OPEN a defined cursor, or use the OPEN cursorname RESET command. Don't use the CLOSE command if you place the RESET option on the OPEN command. The RESET option automatically reevaluates the variable value and identifies a new set of data for the cursor. OPEN cursorname RESET is commonly used with nested cursors. The second cursor is dependent on a variable fetched by the first cursor. By using RESET, you won't need to CLOSE the inner cursor each time. Using the RESET option on OPEN is faster using than the OPEN, CLOSE sequence of commands. Scrolling cursors Normally, cursors move through the data in one direction only, from top to bottom. They move forward one-by-one through the set of defined rows. Once a row has been accessed and passed over, you can't get back to it. The rows can be ordered in the cursor definition - the top to bottom order is not necessarily the table order. In 4.5 Plus! the SCROLL keyword was added to the DECLARE CURSOR command. Declaring a cursor as a scrolling cursor allows you to use directional keywords with the FETCH command to move forwards and backwards through the set of rows. See "Scrolling Cursors" in this Exchange for more information about using scrolling cursors.