File: prguide7.htm

package info (click to toggle)
solid-doc 2.2-1
  • links: PTS
  • area: non-free
  • in suites: potato, slink
  • size: 3,436 kB
  • ctags: 11,371
  • sloc: makefile: 58; sh: 2
file content (552 lines) | stat: -rw-r--r-- 43,342 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
<HTML>
<HEAD>
<TITLE>prguide.htm</TITLE>
<LINK REL="ToC" HREF="httoc.htm">
<LINK REL="Index" HREF="htindex.htm">
<LINK REL="Next" HREF="prguide8.htm">
<LINK REL="Previous" HREF="prguide6.htm"></HEAD>
<BODY BGCOLOR="#FFFFFF">
<P ALIGN=CENTER>
<A HREF="prguide6.htm" TARGET="_self"><IMG SRC="graprev.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Previous Page"></A>
<A HREF="httoc.htm" TARGET="_self"><IMG SRC="gratoc.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="TOC"></A>
<A HREF="htindex.htm" TARGET="_self"><IMG SRC="graindex.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Index"></A>
<A HREF="prguide8.htm" TARGET="_self"><IMG SRC="granext.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Next Page"></A>
<HR ALIGN=CENTER>
<P>
<UL>
<LI>
<A HREF="#E9E8" >Retrieving Results</A>
<UL>
<LI>
<A HREF="#E10E28" >Assigning Storage for Results (Binding)</A>
<LI>
<A HREF="#E10E29" >Determining the Characteristics of a Result Set</A>
<LI>
<A HREF="#E10E30" >Fetching Result Data</A>
<LI>
<A HREF="#E10E31" >Using Cursors</A>
<LI>
<A HREF="#E10E32" >ODBC Extensions for Results</A>
<UL>
<LI>
<A HREF="#E11E29" >Retrieving Data from Unbound Columns</A>
<LI>
<A HREF="#E11E30" >Assigning Storage for Rowsets (Binding)</A>
<UL>
<LI>
<A HREF="#E12E14" >Column-Wise Binding</A>
<LI>
<A HREF="#E12E15" >Row-Wise Binding</A></UL>
<LI>
<A HREF="#E11E31" >Retrieving Rowset Data</A>
<LI>
<A HREF="#E11E32" >Using Block and Scrollable Cursors</A>
<UL>
<LI>
<A HREF="#E12E16" >Block Cursors</A>
<LI>
<A HREF="#E12E17" >Scrollable Cursors</A>
<LI>
<A HREF="#E12E18" >Specifying the Cursor Type</A>
<LI>
<A HREF="#E12E19" >Specifying Cursor Concurrency</A></UL>
<LI>
<A HREF="#E11E33" >Using Bookmarks</A>
<LI>
<A HREF="#E11E34" >Modifying Result Set Data</A>
<UL>
<LI>
<A HREF="#E12E20" >Executing Positioned Update and Delete Statements</A>
<LI>
<A HREF="#E12E21" >Modifying Data with SQLSetPos</A></UL>
<LI>
<A HREF="#E11E35" >Processing Multiple Results</A></UL></UL></UL>
<HR ALIGN=CENTER>
<A NAME="E9E8"></A>
<H1>
<FONT FACE="Arial"><B>RETRIEVING RESULTS</B><A NAME="I2"></A></FONT></H1>
<BR>
<BLOCKQUOTE>
<P>A <B>SELECT</B> statement is used to retrieve data that meets a given set of specifications. For example, <B>SELECT * FROM EMPLOYEE WHERE </B><B>EMPNAME = &quot;Jones&quot;</B> is used to retrieve all columns of all rows in EMPLOYEE where the employee&#146;s name is Jones. ODBC extension functions also can retrieve data. For example, <B>SQLColumns</B> retrieves data about columns in the data source. These sets of data, called result sets, can contain zero or more rows.<A NAME="I3"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Note that other SQL statements, such as <B>GRANT</B> or <B>REVOKE</B>, do not return result sets. For these statements, the return code from <B>SQLExecute</B> or <B>SQLExecDirect</B> is usually the only source of information as to whether the statement was successful. (For <B>INSERT</B>, <B>UPDATE</B>, and <B>DELETE</B> statements, an application can call <B>SQLRowCount</B> to return the number of affected rows.)<A NAME="I4"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>The steps an application takes to process a result set depends on what is known about it.<A NAME="I5"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI><B>Known result set    </B>The application knows the exact form of the SQL statement, and therefore the result set, at compile time. For example, the query <B>SELECT</B> <B>EMPNO</B>, <B>EMPNAME FROM EMPLOYEE</B> returns two specific columns.<A NAME="I6"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI><B>Unknown result set    </B>The application does not know the exact form of the SQL statement, and therefore the result set, at compile time. For example, the ad hoc query <B>SELECT * FROM EMPLOYEE</B> returns all currently defined columns in the EMPLOYEE table. The application may not be able to predict the format of these results prior to execution.
</BLOCKQUOTE></UL>
<A NAME="E10E28"></A>
<H2>
<FONT FACE="Arial"><B>Assigning Storage for Results (Binding)</B><A NAME="I7"></A></FONT></H2>
<BLOCKQUOTE>
<P>An application can assign storage for results before or after it executes an SQL statement. If an application prepares or executes the SQL statement first, it can inquire about the result set before it assigns storage for results. For example, if the result set is unknown, the application must retrieve the number of columns before it can assign storage for them.<A NAME="I8"></A><A NAME="I9"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To associate storage for a column of data, an application calls <B>SQLBindCol</B> and passes it the following information:<A NAME="I10"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>The data type to which the data is to be converted. For more information, see &quot;Converting Data from SQL to C Data Types&quot; in Appendix D, &quot;Data Types.&quot;<A NAME="I11"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The address of an output buffer for the data. The application must allocate this buffer and it must be large enough to hold the data in the form to which it is converted.<A NAME="I12"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The length of the output buffer. This value is ignored if the returned data has a fixed width in C, such as an integer, real number, or date structure.<A NAME="I13"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The address of a storage buffer in which to return the number of bytes of available data.
</BLOCKQUOTE></UL>
<A NAME="E10E29"></A>
<H2>
<FONT FACE="Arial"><B>Determining the Characteristics of a Result Set</B><A NAME="I14"></A></FONT></H2>
<BLOCKQUOTE>
<P>To determine the characteristics of a result set, an application can:<A NAME="I15"></A><A NAME="I16"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>Call <B>SQLNumResultCols</B> to determine how many columns a request returned.<A NAME="I17"></A><A NAME="I18"></A><A NAME="I19"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>Call <B>SQLColAttributes</B> or <B>SQLDescribeCol</B> to describe a column in the result set.<A NAME="I20"></A><A NAME="I21"></A><A NAME="I22"></A><A NAME="I23"></A><A NAME="I24"></A>
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<P>If the result set is unknown, an application can use the information returned by these functions to bind the columns in the result set. An application can call these functions at any time after a statement is prepared or executed. Note that, although <B>SQLRowCount</B> can sometimes return the number of rows in a result set, it is not guaranteed to do so. Few data sources support this functionality and interoperable applications should not rely on it.<A NAME="I25"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note For optimal performance, an application should call <B>SQLColAttributes</B>, <B>SQLDescribeCol</B>, and <B>SQLNumResultCols</B> after a statement is executed. In data sources that emulate statement preparation, these functions sometimes execute more slowly before a statement is executed because the information returned by them is not readily available until after the statement is executed.</NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<A NAME="E10E30"></A>
<H2>
<FONT FACE="Arial"><B>Fetching Result Data</B><A NAME="I26"></A><A NAME="I27"></A><A NAME="I28"></A><A NAME="I29"></A></FONT></H2>
<BLOCKQUOTE>
<P>To retrieve a row of data from the result set, an application:<A NAME="I30"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Calls <B>SQLBindCol</B> to bind the columns of the result set to storage locations if it has not already done so.<A NAME="I31"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Calls <B>SQLFetch</B> to move to the next row in the result set and retrieve data for all bound columns.<A NAME="I32"></A>
</BLOCKQUOTE></UL>
<A NAME="E10E31"></A>
<H2>
<FONT FACE="Arial"><B>Using Cursors</B><A NAME="I33"></A><A NAME="I34"></A></FONT></H2>
<BLOCKQUOTE>
<P>To keep track of its position in the result set, a driver maintains a cursor. The cursor is so named because it indicates the current position in the result set, just as the cursor on a CRT screen indicates current position.<A NAME="I35"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Each time an application calls <B>SQLFetch</B>, the driver moves the cursor to the next row and returns that row. The cursor supported by the core ODBC functions only scrolls forward, one row at a time. (To reretrieve a row of data that it has already retrieved from the result set, the application must close the cursor by calling <B>SQLFreeStmt</B> with the SQL_CLOSE option, reexecute the <B>SELECT</B> statement, and fetch rows with <B>SQLFetch</B> until the target row is retrieved.)<A NAME="I36"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Important Committing or rolling back a transaction, either by calling <B>SQLTransact</B> or by using the SQL_AUTOCOMMIT connection option, can cause the data source to close the cursors for all <I>hstmts</I> on an <I>hdbc</I>. For more information, see the SQL_CURSOR_COMMIT_BEHAVIOR and SQL_CURSOR_ROLLBACK_BEHAVIOR information types in <B>SQLGetInfo</B>.</NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<A NAME="E10E32"></A>
<H2>
<FONT FACE="Arial"><B>ODBC Extensions for Results</B><A NAME="I37"></A></FONT></H2>
<BLOCKQUOTE>
<P>ODBC extends the X/Open and SQL Access Group Call Level Interface to provide additional functions related to retrieving results. The remainder of this chapter describes these functions. To determine if a driver supports a specific function, an application calls <B>SQLGetFunctions</B>.
</BLOCKQUOTE>
<A NAME="E11E29"></A>
<H3>
<FONT FACE="Arial">Retrieving Data from Unbound Columns<A NAME="I38"></A><A NAME="I39"></A><A NAME="I40"></A><A NAME="I41"></A><A NAME="I42"></A><A NAME="I43"></A><A NAME="I44"></A></FONT></H3>
<BLOCKQUOTE>
<P>To retrieve data from unbound columns &#151; that is, columns for which storage has not been assigned with <B>SQLBindCol</B> &#151; an application uses <B>SQLGetData</B>. The application first calls <B>SQLFetch</B> or <B>SQLExtendedFetch</B> to position the cursor on the next row. It then calls <B>SQLGetData</B> to retrieve data from specific unbound columns.<A NAME="I45"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>An application may retrieve data from both bound and unbound columns in the same row. It calls <B>SQLBindCol</B> to bind as many columns as desired. It calls <B>SQLFetch</B> or <B>SQLExtendedFetch</B> to position the cursor on the next row of the result set and retrieve all bound columns. It then calls <B>SQLGetData</B> to retrieve data from unbound columns.<A NAME="I46"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>If the data type of a column is character, binary, or data source&#150;specific and the column contains more data than can be retrieved in a single call, an application may call <B>SQLGetData</B> more than once for that column, as long as the data is being transferred to a buffer of type SQL_C_CHAR or SQL_C_BINARY. For example, data of the SQL_LONGVARBINARY and SQL_LONGVARCHAR types may need to be retrieved in several parts.<A NAME="I47"></A><A NAME="I48"></A><A NAME="I49"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>For maximum interoperability, an application should only call <B>SQLGetData</B> for columns to the right of the rightmost bound column and then only in left-to-right order. To determine if a driver can return data with <B>SQLGetData</B> for any column (including unbound columns before the last bound column and any bound columns) or in any order, an application calls <B>SQLGetInfo</B> with the SQL_GETDATA_EXTENSIONS option.
</BLOCKQUOTE>
<A NAME="E11E30"></A>
<H3>
<FONT FACE="Arial">Assigning Storage for Rowsets (Binding)<A NAME="I50"></A></FONT></H3>
<BLOCKQUOTE>
<P>In addition to binding individual rows of data, an application can call <B>SQLBindCol</B> to assign storage for a <I>rowset</I> (one or more rows of data). By default, rowsets are bound in column-wise fashion. They can also be bound in row-wise fashion.<A NAME="I51"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To specify how many rows of data are in a rowset, an application calls <B>SQLSetStmtOption</B> with the SQL_ROWSET_SIZE option.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E14"></A>
<H4>
<FONT>Column-Wise Binding<A NAME="I52"></A><A NAME="I53"></A><A NAME="I54"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To assign storage for column-wise bound results, an application performs the following steps for each column to be bound:<A NAME="I55"></A><A NAME="I56"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Allocates an array of data storage buffers. The array has as many elements as there are rows in the rowset.<A NAME="I57"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Allocates an array of storage buffers to hold the number of bytes available to return for each data value. The array has as many elements as there are rows in the rowset.<A NAME="I58"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>3. Calls <B>SQLBindCol</B> and specifies the address of the data array, the size of one element of the data array, the address of the number-of-bytes array, and the type to which the data will be converted. When data is retrieved, the driver will use the array element size to determine where to store successive rows of data in the array.
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<A NAME="E12E15"></A>
<H4>
<FONT>Row-Wise Binding<A NAME="I59"></A><A NAME="I60"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To assign storage for row-wise bound results, an application performs the following steps:<A NAME="I61"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Declares a structure that can hold a single row of retrieved data and the associated data lengths. (For each column to be bound, the structure contains one field to contain data and one field to contain the number of bytes of data available to return.)<A NAME="I62"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Allocates an array of these structures. This array has as many elements as there are rows in the rowset.<A NAME="I63"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>3. Calls <B>SQLBindCol</B> for each column to be bound. In each call, the application specifies the address of the column&#146;s data field in the first array element, the size of the data field, the address of the column&#146;s number-of-bytes field in the first array element, and the type to which the data will be converted.<A NAME="I64"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>4. Calls <B>SQLSetStmtOption</B> with the SQL_BIND_TYPE option and specifies the size of the structure. When the data is retrieved, the driver will use the structure size to determine where to store successive rows of data in the array.
</BLOCKQUOTE></UL>
<A NAME="E11E31"></A>
<H3>
<FONT FACE="Arial">Retrieving Rowset Data<A NAME="I65"></A><A NAME="I66"></A><A NAME="I67"></A></FONT></H3>
<BLOCKQUOTE>
<P>Before it retrieves rowset data, an application calls <B>SQLSetStmtOption</B> with the SQL_ROWSET_SIZE option to specify the number of rows in the rowset. It then binds columns in the rowset with <B>SQLBindCol</B>. The rowset may be bound in column-wise or row-wise fashion. For more information, see &quot;Assigning Storage for Rowsets (Binding)&quot; previous in this chapter.<A NAME="I68"></A><A NAME="I69"></A><A NAME="I70"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To retrieve rowset data, an application calls <B>SQLExtendedFetch</B>.<A NAME="I71"></A><A NAME="I72"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>For maximum interoperability, an application should not use <B>SQLGetData</B> to retrieve data from unbound columns in a block (more than one row) of data that has been retrieved with <B>SQLExtendedFetch</B>. To determine if a driver can return data with <B>SQLGetData</B> from a block of data, an application calls <B>SQLGetInfo</B> with the SQL_GETDATA_EXTENSIONS option.
</BLOCKQUOTE>
<A NAME="E11E32"></A>
<H3>
<FONT FACE="Arial">Using Block and Scrollable Cursors<A NAME="I73"></A></FONT></H3>
<BLOCKQUOTE>
<P>As originally designed, cursors in SQL only scroll forward through a result set, returning one row at a time. However, interactive applications often require forward and backward scrolling, absolute or relative positioning within the result set, and the ability to retrieve and update blocks of data, or <I>rowsets</I>.<A NAME="I74"></A><A NAME="I75"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To retrieve and update rowset data, ODBC provides a <I>block</I> cursor attribute. To allow an application to scroll forwards or backwards through the result set, or move to an absolute or relative position in the result set, ODBC provides a <I>scrollable</I> cursor attribute. Cursors may have one or both attributes.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E16"></A>
<H4>
<FONT>Block Cursors<A NAME="I76"></A><A NAME="I77"></A><A NAME="I78"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>An application calls <B>SQLSetStmtOption</B> with the SQL_ROWSET_SIZE option to specify the rowset size. The application can call <B>SQLSetStmtOption</B> to change the rowset size at any time. Each time the application calls <B>SQLExtendedFetch</B>, the driver returns the next <I>rowset size</I> rows of data. After the data is returned, the cursor points to the first row in the rowset. By default, the rowset size is one.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E17"></A>
<H4>
<FONT>Scrollable Cursors<A NAME="I79"></A><A NAME="I80"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Applications have different needs in their ability to sense changes in the tables underlying a result set. For example, when balancing financial data, an accountant needs data that appears static; it is impossible to balance books when the data is continually changing. When selling concert tickets, a clerk needs up-to-the minute, or dynamic, data on which tickets are still available. Various cursor models are designed to meet these needs, each of which requires different sensitivities to changes in the tables underlying the result set.
</BLOCKQUOTE>
<BLOCKQUOTE>
<H5><B>Static Cursors</B><A NAME="I81"></A><A NAME="I82"></A></H5>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>At one extreme are <I>static</I> cursors, to which the data in the underlying tables appears to be static. The membership, order, and values in the result set used by a static cursor are generally fixed when the cursor is opened. Rows updated, deleted, or inserted by other users (including other cursors in the same application) are not detected by the cursor until it is closed and then reopened; the SQL_STATIC_SENSITIVITY information type returns whether the cursor can detect rows it has updated, deleted, or inserted.<A NAME="I83"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Static cursors are commonly implemented by taking a snapshot of the data or locking the result set. Note that in the former case, the cursor diverges from the underlying tables as other users make changes; in the latter case, other users are prohibited from changing the data.
</BLOCKQUOTE>
<BLOCKQUOTE>
<H5><B>Dynamic Cursors</B><A NAME="I84"></A><A NAME="I85"></A></H5>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>At the other extreme are <I>dynamic</I> cursors, to which the data appears to be dynamic. The membership, order, and values in the result set used by a dynamic cursor are ever-changing. Rows updated, deleted, or inserted by all users (the cursor, other cursors in the same application, and other applications) are detected by the cursor when data is next fetched. Although ideal for many situations, dynamic cursors are difficult to implement.
</BLOCKQUOTE>
<BLOCKQUOTE>
<H5><B>Keyset-Driven Cursors</B><A NAME="I86"></A><A NAME="I87"></A></H5>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Between static and dynamic cursors are <I>keyset-driven</I> cursors, which have some of the attributes of each. Like static cursors, the membership and ordering of the result set of a keyset-driven cursor is generally fixed when the cursor is opened. Like dynamic cursors, most changes to the values in the underlying result set are visible to the cursor when data is next fetched.<A NAME="I88"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>When a keyset-driven cursor is opened, the driver saves the keys for the entire result set, thus fixing the membership and order of the result set. As the cursor scrolls through the result set, the driver uses the keys in this <I>keyset</I> to retrieve the current data values for each row in the rowset. Because data values are retrieved only when the cursor scrolls to a given row, updates to that row by other users (including other cursors in the same application) after the cursor was opened are visible to the cursor.<A NAME="I89"></A><A NAME="I90"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>If the cursor scrolls to a row of data that has been deleted by other users (including other cursors in the same application), the row appears as a <I>hole</I> in the result set, since the key is still in the keyset but the row is no longer in the result set. Updating the key values in a row is considered to be deleting the existing row and inserting a new row; therefore, rows of data for which the key values have been changed also appear as holes. When the driver encounters a hole in the result set, it returns a status code of SQL_ROW_DELETED for the row.<A NAME="I91"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Rows of data inserted into the result set by other users (including other cursors in the same application) after the cursor was opened are not visible to the cursor, since the keys for those rows are not in the keyset.<A NAME="I92"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>The SQL_STATIC_SENSITIVITY information type returns whether the cursor can detect rows it has deleted or inserted. Because updating key values in a keyset-driven cursor is considered to be deleting the existing row and inserting a new row, keyset-driven cursors can always detect rows they have updated.
</BLOCKQUOTE>
<BLOCKQUOTE>
<H5><B>Mixed (Keyset/Dynamic) Cursors</B><A NAME="I93"></A><A NAME="I94"></A></H5>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>If a result set is large, it may be impractical for the driver to save the keys for the entire result set. Instead, the application can use a <I>mixed</I> cursor. In a mixed cursor, the keyset is smaller than the result set, but larger than the rowset.<A NAME="I95"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Within the boundaries of the keyset, a mixed cursor is keyset-driven, that is, the driver uses keys to retrieve the current data values for each row in the rowset. When a mixed cursor scrolls beyond the boundaries of the keyset, it becomes dynamic, that is, the driver simply retrieves the next <I>rowset size</I> rows of data. The driver then constructs a new keyset, which contains the new rowset.<A NAME="I96"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>For example, assume a result set has 1000 rows and uses a mixed cursor with a keyset size of 100 and a rowset size of 10. When the cursor is opened, the driver (depending on the implementation) saves keys for the first 100 rows and retrieves data for the first 10 rows. If another user deletes row 11 and the cursor then scrolls to row 11, the cursor will detect a hole in the result set; the key for row 11 is in the keyset but the data is no longer in the result set. This is the same behavior as a keyset-driven cursor. However, if another user deletes row 101 and the cursor then scrolls to row 101, the cursor will not detect a hole; the key for the row 101 is not in the keyset. Instead, the cursor will retrieve the data for the row that was originally row 102. This is the same behavior as a dynamic cursor.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E18"></A>
<H4>
<FONT>Specifying the Cursor Type<A NAME="I97"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To specify the cursor type, an application calls <B>SQLSetStmtOption</B> with the SQL_CURSOR_TYPE option. The application can specify a cursor that only scrolls forward, a static cursor, a dynamic cursor, a keyset-driven cursor, or a mixed cursor. If the application specifies a mixed cursor, it also specifies the size of the keyset used by the cursor.<A NAME="I98"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note To use the ODBC cursor library, an application calls <B>SQLSetConnectOption</B> with the SQL_ODBC_CURSORS option before it connects to the data source. The cursor library supports block scrollable cursors. It also supports positioned update and delete statements.<A NAME="I99"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Unless the cursor is a forward-only cursor, an application calls <B>SQLExtendedFetch</B> to scroll the cursor backwards, forwards, or to an absolute or relative position in the result set. The application calls <B>SQLSetPos</B> to refresh the row currently pointed to by the cursor.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E19"></A>
<H4>
<FONT>Specifying Cursor Concurrency<A NAME="I100"></A><A NAME="I101"></A><A NAME="I102"></A><A NAME="I103"></A><A NAME="I104"></A><A NAME="I105"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P><I>Concurrency</I> is the ability of more than one user to use the same data at the same time. A transaction is <I>serializable</I> if it is performed in a manner in which it appears as if no other transactions operate on the same data at the same time. For example, assume one transaction doubles data values and another adds 1 to data values. If the transactions are serializable and both attempt to operate on the values 0 and 10 at the same time, the final values will be 1 and 21 or 2 and 22, depending on which transaction is performed first. If the transactions are not serializable, the final values will be 1 and 21, 2 and 22, 1 and 22, or 2 and 21; the sets of values 1 and 22, and 2 and 21, are the result of the transactions acting on each value in a different order.<A NAME="I106"></A><A NAME="I107"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Serializability is considered necessary to maintain database integrity. For cursors, it is most easily implemented at the expense of concurrency by locking the result set. A compromise between serializability and concurrency is <I>optimistic concurrency control</I>. In a cursor using optimistic concurrency control, the driver does not lock rows when it retrieves them. When the application requests an update or delete operation, the driver or data source checks if the row has changed. If the row has not changed, the driver or data source prevents other transactions from changing the row until the operation is complete. If the row has changed, the transaction containing the update or delete operation fails.<A NAME="I108"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To specify the concurrency used by a cursor, an application calls <B>SQLSetStmtOption</B> with the SQL_CONCURRENCY option. The application can specify that the cursor is read-only, locks the result set, uses optimistic concurrency control and compares row versions to determine if a row has changed, or uses optimistic concurrency control and compares data values to determine if a row has changed. The application calls <B>SQLSetPos</B> to lock the row currently pointed to by the cursor, regardless of the specified cursor concurrency.
</BLOCKQUOTE>
<A NAME="E11E33"></A>
<H3>
<FONT FACE="Arial">Using Bookmarks<A NAME="I109"></A><A NAME="I110"></A></FONT></H3>
<BLOCKQUOTE>
<P>A bookmark is a 32-bit value that an application uses to return to a row. The application does not request that the driver places a bookmark on a row; instead, the application requests a bookmark that it can use to return to a row. For example, if a bookmark is a row number, an application requests the row number of a row and stores it. Later, the application passes this row number back to the driver and requests that the driver return to the row.<A NAME="I111"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Before opening the cursor, an application must call <B>SQLSetStmtOption</B> with the SQL_USE_BOOKMARKS option to inform the driver it will use bookmarks. After opening the cursor, the application retrieves bookmarks either from column 0 of the result set or by calling <B>SQLGetStmtOption</B> with the SQL_GET_BOOKMARK option. To retrieve a bookmark from the result set, the application either binds column 0 and calls <B>SQLExtendedFetch</B> or calls <B>SQLGetData</B>; in either case, the <I>fCType</I> argument must be set to SQL_C_BOOKMARK. To return to the row specified by a bookmark, the application calls <B>SQLExtendedFetch</B> with a fetch type of SQL_FETCH_BOOKMARK.<A NAME="I112"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>If a bookmark requires more than 32 bits, such as when it is a key value, the driver maps the bookmarks requested by the application to 32-bit binary values. The 32-bit binary values are then returned to the application. Because this mapping may require considerable memory, applications should only bind column 0 of the result set if they will actually use bookmarks for most rows. Otherwise, they should call <B>SQLGetStmtOption</B> with the SQL_BOOKMARK statement option or call <B>SQLGetData</B> for column 0.<A NAME="I113"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Before an application opens a cursor with which it will use bookmarks, it:<A NAME="I114"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>Calls <B>SQLSetStmtOption</B> with the SQL_USE_BOOKMARKS option and a value of SQL_UB_ON.<A NAME="I115"></A>
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<P>To retrieve a bookmark for the current row, an application:<A NAME="I116"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>Retrieves the value from column 0 of the rowset. The application can either call <B>SQLBindCol</B> to bind column 0 before it calls <B>SQLExtendedFetch</B> or call <B>SQLGetData</B> to retrieve the data after it calls <B>SQLExtendedFetch</B>. In either case, the <I>fCType</I> argument must be SQL_C_BOOKMARK.
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note To determine whether it can call <B>SQLGetData</B> for a block (more than one row) of data and whether it can call <B>SQLGetData</B> for a column before the last bound column, an application calls <B>SQLGetInfo</B> with the SQL_GETDATA_EXTENSIONS information type.<A NAME="I117"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>  &#150; Or &#150;<A NAME="I118"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI> Calls <B>SQLSetPos</B> with the SQL_POSITION option to position the cursor on the row and calls <B>SQLGetStmtOption</B> with the SQL_BOOKMARK option to retrieve the bookmark.<A NAME="I119"></A>
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<P>To return to the row specified by a bookmark (or a row a certain number of rows from the bookmark), an application:<A NAME="I120"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>Calls <B>SQLExtendedFetch</B> with the <I>irow</I> argument set to the bookmark and the <I>fFetchType</I> argument set to SQL_FETCH_BOOKMARK. The driver returns the rowset starting with the row identified by the bookmark.
</BLOCKQUOTE></UL>
<A NAME="E11E34"></A>
<H3>
<FONT FACE="Arial">Modifying Result Set Data<A NAME="I121"></A><A NAME="I122"></A></FONT></H3>
<BLOCKQUOTE>
<P>ODBC provides two ways to modify data in the result set. Positioned update and delete statements are similar to such statements in embedded SQL. Calls to <B>SQLSetPos</B> allow an application to update, delete, or add new data without executing SQL statements.
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E20"></A>
<H4>
<FONT>Executing Positioned Update and Delete Statements<A NAME="I123"></A><A NAME="I124"></A><A NAME="I125"></A><A NAME="I126"></A><A NAME="I127"></A><A NAME="I128"></A><A NAME="I129"></A><A NAME="I130"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>An application can update or delete the row in the result set currently pointed to by the cursor. This is known as a positioned update or delete statement. After executing a <B>SELECT</B> statement to create a result set, an application calls <B>SQLFetch</B> one or more times to position the cursor on the row to be updated or deleted. Alternatively, it fetches the rowset with <B>SQLExtendedFetch</B> and positions the cursor on the desired row by calling <B>SQLSetPos</B> with the SQL_POSITION option. To update or delete the row, the application then executes an SQL statement with the following syntax on a different <I>hstmt</I>:
</BLOCKQUOTE>
<BLOCKQUOTE>
<PRE><B>UPDATE </B><I>table-name</I>
<BR>   <B>SET</B> <I>column-identifier</I> <B>= </B>{<I>expression</I> |<B> NULL</B>}
<BR><B>   </B>[<B>,</B> <I>column-identifier</I> <B>=</B> {<I>expression</I> |<B> NULL</B>}]...
<BR>   <B>WHERE CURRENT OF</B> <I>cursor-name</I>
<B>DELETE FROM </B><I>table-name</I> <B>WHERE CURRENT OF</B> <I>cursor-name</I><A NAME="I131"></A></PRE></BLOCKQUOTE>
<BLOCKQUOTE>
<P>Positioned update and delete statements require cursor names. An application can name a cursor with <B>SQLSetCursorName</B>. If the application has not named the cursor by the time the driver executes a <B>SELECT</B> statement, the driver generates a cursor name. To retrieve the cursor name for an <I>hstmt</I>, an application calls <B>SQLGetCursorName</B>.<A NAME="I132"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To execute a positioned update or delete statement, an application must follow these guidelines:<A NAME="I133"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>The <B>SELECT</B> statement that creates the result set must use a <B>FOR UPDATE</B> clause.<A NAME="I134"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The cursor name used in the <B>UPDATE</B> or <B>DELETE</B> statement must be the same as the cursor name associated with the <B>SELECT</B> statement.<A NAME="I135"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The application must use different <I>hstmts</I> for the <B>SELECT</B> statement and the <B>UPDATE</B> or <B>DELETE</B> statement.<A NAME="I136"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>The <I>hstmts</I> for the <B>SELECT</B> statement and the <B>UPDATE</B> or <B>DELETE</B> statement must be on the same connection.<A NAME="I137"></A>
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<P>To determine if a data source supports positioned update and delete statements, an application calls <B>SQLGetInfo</B> with the SQL_POSITIONED_STATEMENTS option. For an example of code that performs a positioned update in a rowset, see <B>SQLSetPos</B> in Chapter 13, &quot;Function Reference.&quot;<A NAME="I138"></A><A NAME="I139"></A><A NAME="I140"></A><A NAME="I141"></A><A NAME="I142"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note In ODBC 1.0, positioned update, positioned delete, and <B>SELECT FOR </B><B>UPDATE</B> statements were part of the core SQL grammar; in ODBC 2.0, they are part of the extended grammar. Applications that use the SQL conformance level to determine whether these statements are supported also need to check the version number of the driver to correctly interpret the information. In particular, applications that use these features with ODBC 1.0 drivers need to explicitly check for these capabilities in ODBC 2.0 drivers.</NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<A NAME="E12E21"></A>
<H4>
<FONT>Modifying Data with SQLSetPos<A NAME="I143"></A><A NAME="I144"></A></FONT></H4>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To add, update, and delete rows of data, an application calls <B>SQLSetPos</B> and specifies the operation, the row number, and how to lock the row. Where new rows of data are added to the result set, and whether they are visible to the cursor is data source&#150;defined.<A NAME="I145"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>The row number determines both the number of the row in the rowset to update or delete and the index of the row in the rowset buffers from which to retrieve data to add or update. If the row number is 0, the operation affects all of the rows in the rowset.<A NAME="I146"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P><B>SQLSetPos</B> retrieves the data to update or add from the rowset buffers. It only updates those columns in a row that have been bound with <B>SQLBindCol</B> and do not have a length of SQL_IGNORE. However, it cannot add a new row of data unless all of the columns in the row are bound, are nullable, or have a default value.<A NAME="I147"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note The rowset buffers are used both to send and retrieve data. To avoid overwriting existing data when it adds a new row of data, an application can allocate an extra row at the end of the rowset buffers to use as an add buffer.<A NAME="I148"></A><A NAME="I149"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To add a new row of data to the result set, an application:<A NAME="I150"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Places the data for each column the <I>rgbValue</I> buffers specified with <B>SQLBindCol</B>. To avoid overwriting an existing row of data, the application should allocate an extra row of the rowset buffers to use as an add buffer.<A NAME="I151"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Places the length of each column in the <I>pcbValue</I> buffer specified with <B>SQLBindCol</B>; this only needs to be done for columns with an <I>fCType</I> of SQL_C_CHAR or SQL_C_BINARY. To use the default value for a column, the application specifies a length of SQL_IGNORE.
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note To add a new row of data to a result set, one of the following two conditions must be met:</NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>All columns in the underlying tables must be bound 
<BR>with <B>SQLBindCol</B>.</NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>All unbound columns and all bound columns for 
<BR>which the specified length is SQL_IGNORE must 
<BR>accept NULL values or have default values.<A NAME="I152"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI> To determine if a row in a result set accepts NULL values, an application calls <B>SQLColAttributes</B>. To determine if a data source supports non-nullable columns, an application calls <B>SQLGetInfo</B> with the SQL_NON_NULLABLE flag.<A NAME="I153"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>3. Calls <B>SQLSetPos</B> with the <I>fOption</I> argument set to SQL_ADD. The <I>irow</I> argument determines the row in the rowset buffers from which the data is retrieved. For information about how an application sends data for data-at-execution columns, see <B>SQLSetPos</B> in Chapter 13, &quot;Function Reference.&quot;<A NAME="I154"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI> After the row is added, the row the cursor points to is unchanged.
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note Columns for long data types, such as SQL_LONGVARCHAR and SQL_LONGVARBINARY, are generally not bound. However, if an application uses <B>SQLSetPos</B> to send data for these columns, it must bind them with <B>SQLBindCol</B>. Unless the driver returns the SQL_GD_BOUND bit for the SQL_GETDATA_EXTENSIONS information type, the application must unbind them before calling <B>SQLGetData</B> to retrieve data from them.<A NAME="I155"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To update a row of data, an application:<A NAME="I156"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Modifies the data of each column to be updated in the <I>rgbValue</I> buffer specified with <B>SQLBindCol</B>.<A NAME="I157"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Places the length of each column to be updated in the <I>pcbValue</I> buffer specified with <B>SQLBindCol</B>. This only needs to be done for columns with an <I>fCType</I> of SQL_C_CHAR or SQL_C_BINARY.<A NAME="I158"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>3. Sets the value of the <I>pcbValue</I> buffer for each bound column that is not to be updated to SQL_IGNORE.<A NAME="I159"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>4. Calls <B>SQLSetPos</B> with the <I>fOption</I> argument set to SQL_UPDATE. The <I>irow</I> argument specifies the number of the row in the rowset to modify and the index of row in the rowset buffer from which to retrieve the data. The cursor points to this row after it is updated.<A NAME="I160"></A>
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<P>For information about how an application sends data for data-at-execution columns, see <B>SQLSetPos</B> in Chapter 13, &quot;Function Reference.&quot;<A NAME="I161"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To delete a row of data, an application:<A NAME="I162"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>Calls <B>SQLSetPos</B> with the <I>fOption</I> argument set to SQL_DELETE. The <I>irow</I> argument specifies the number of the row in the rowset to delete. The cursor points to this row after it is deleted.
</BLOCKQUOTE></UL>
<BLOCKQUOTE>
<HR ALIGN=CENTER>
<NOTE>Note The application cannot perform any positioned operations, such as executing a positioned update or delete statement or calling <B>SQLGetData</B>, on a deleted row.<A NAME="I163"></A></NOTE>
<HR ALIGN=CENTER>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To determine what operations a data source supports for <B>SQLSetPos</B>, an application calls <B>SQLGetInfo</B> with the SQL_POS_OPERATIONS flag.
</BLOCKQUOTE>
<A NAME="E11E35"></A>
<H3>
<FONT FACE="Arial">Processing Multiple Results<A NAME="I164"></A><A NAME="I165"></A></FONT></H3>
<BLOCKQUOTE>
<P><B>SELECT</B> statements return result sets. <B>UPDATE</B>, <B>INSERT</B>, and <B>DELETE</B> statements return a count of affected rows. If any of these statements are batched, submitted with arrays of parameters, or in procedures, they can return multiple result sets or counts.<A NAME="I166"></A><A NAME="I167"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>To process a batch of statements, statement with arrays of parameters, or procedure returning multiple result sets or row counts, an application:<A NAME="I168"></A>
</BLOCKQUOTE>
<UL>
<BLOCKQUOTE>
<LI>1. Calls <B>SQLExecute</B> or <B>SQLExecDirect</B> to execute the statement or procedure.<A NAME="I169"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>2. Calls <B>SQLRowCount</B> to determine the number of rows affected by an <B>UPDATE</B>, <B>INSERT</B>, or <B>DELETE</B> statement. For statements or procedures that return result sets, the application calls functions to determine the characteristics of the result set and retrieve data from the result set.<A NAME="I170"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>3. Calls <B>SQLMoreResults</B> to determine if another result set or row count is available.<A NAME="I171"></A>
</BLOCKQUOTE>
<BLOCKQUOTE>
<LI>4. Repeats steps 2 and 3 until <B>SQLMoreResults</B> returns SQL_NO_DATA_FOUND.
</BLOCKQUOTE></UL><P ALIGN=CENTER>
<A HREF="prguide6.htm" TARGET="_self"><IMG SRC="graprev.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Previous Page"></A>
<A HREF="httoc.htm" TARGET="_self"><IMG SRC="gratoc.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="TOC"></A>
<A HREF="htindex.htm" TARGET="_self"><IMG SRC="graindex.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Index"></A>
<A HREF="prguide8.htm" TARGET="_self"><IMG SRC="granext.gif" WIDTH = 32 HEIGHT = 32 BORDER = 0 ALT="Next Page"></A>

<center><p><font SIZE=-2>Copyright &copy; 1992-1997 Solid Information Technology Ltd All rights reserved.</font></p></center>
</BODY></HTML>