A cursor allows looping through a record set and performing a certain operation on each record within the set. SQL Server supports three functions that can help you while working with cursors: @@FETCH_STATUS, @@CURSOR_ROWS and CURSOR_STATUS. Cursor functions are non-deterministic.
A cursor life cycle can be described as follows:
- Cursor is declared using the DECLARE CURSOR statement. This statement creates a cursor within SQL Server memory
- Cursor is activated using OPEN CURSOR statement. At this point you can populate the cursor with a record set.
- Data is retrieved from the cursor using the FETCH keyword.
- A WHILE loop is executed within the cursor to perform some operation with the rows in the cursor with the condition that the FETCH command is successful.
- Cursor is deactivated using CLOSE CURSOR statement. At this point you can't populate the cursor with additional rows. Nor can you work with rows within the cursor. However, you can re-open the cursor with OPEN CURSOR statement and perform additional work with the cursor.
- The cursor is destroyed using DEALLOCATE CURSOR statement. Once the cursor is de-allocated it cannot be reopened.
@@FETCH
The most commonly used cursor function is @@FETCH_STATUS. This function determines whether FETCH keyword has successfully retrieved a row from the current cursor.
@@FETCH_STATUS can take one of the three values:
| @@FETCH_STATUS value | Meaning |
|---|---|
| 0 | Successful fetch of a row within a cursor |
| -1 | Fetch has failed. This could mean that the cursor has reached the beginning (or end) of the record set. This could also mean that we attempted retrieving a record that does not exist. For instance, if you attempt to grab 51st record within a cursor that has 50 records fetch status will be 1. |
| -2 | The fetched row is missing. This means the record you're trying to FETCH has been deleted or its key has been updated since you have opened the cursor. |
For example, the following cursor is populated with the top 5 customer names. While the cursor fetches rows successfully the @@FETCH_STATUS is 0. Once we get to the end of the result set @@FETCH_STATUS becomes -1:
01.DECLARE @customer_full_name VARCHAR(85)02.DECLARE customer_cursor CURSOR FOR SELECT TOP 5 FirstName + ' ' + MiddleName + ' ' + LastName FROM dimCustomer 03.OPEN customer_cursor 04.FETCH NEXT FROM customer_cursor INTO @customer_full_name05.WHILE @@FETCH_STATUS = 0 06.BEGIN -- typically you'd do some row-based operation here 07.FETCH NEXT FROM customer_cursor INTO @customer_full_name 08.SELECT @@FETCH_STATUS AS fetch_status 09.END 10.CLOSE customer_cursor 11.DEALLOCATE customer_cursorResults:
01.fetch_status 02.------------ 03.004.fetch_status 05.------------ 06.007.fetch_status 08.------------ 09.010.fetch_status 11.------------ 12.013.fetch_status 14.------------ 15.-1@@CURSOR_ROWS
@@CURSOR_ROWS function returns the number of rows in the cursor which was opened last on the current connection. This means that if you have 3 cursors open @@CURSOR_ROWS will return the number of rows in the 3rd cursor. @@CURSOR_ROWS can take the following values:
| @@CURSOR_ROWS value | Meaning |
|---|---|
| -m | Cursor is being populated asynchronously. "M" is the value of records in the record set. |
| -1 | The cursor is DYNAMIC; that means, it reflects the changes to the data within the cursor. Therefore the number of rows can change due to addition or deletion of rows in the underlying tables. DYNAMIC cursors always return 1 as value of @@CURSOR_ROWS. |
| 0 | This can mean one of the following:
|
| N | Number of rows in the cursor record set. N is reported after the cursor has been fully populated |
The following example shows you how @@CURSOR_ROWS value changes during the lifetime of the cursor:
01.DECLARE @last_name VARCHAR(20),02.@first_name VARCHAR(20)03. 04.DECLARE MY_CURSOR CURSOR FOR05.SELECT TOP 3 LASTNAME,06.FIRSTNAME07.FROM DIMCUSTOMER08. 09.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'10. 11.OPEN MY_CURSOR12. 13.FETCH NEXT FROM MY_CURSOR14.INTO @last_name,15.@first_name16. 17.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'18. 19.WHILE @@FETCH_STATUS = 020.BEGIN-- typically you'd do some row-based operation here 21.FETCH NEXT FROM MY_CURSOR22.INTO @last_name,23.@first_name24.END25. 26.CLOSE MY_CURSOR27. 28.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'29. 30.DEALLOCATE MY_CURSORResults:
1.---------------------------------------------- 2.cursor has 0 rows 3.---------------------------------------------- 4.cursor has 3 rows 5.---------------------------------------------- 6.cursor has 0 rowsIf the same cursor is executed with DYNAMIC keyword against the entire DimCustomer table within Adventure Works database the results would show "cursor has -1 rows" while the cursor was being searched.
CURSOR_STATUS
The CURSOR_STATUS function can be used effectively within a stored procedure that calls another stored procedure, which returns an output parameter of CURSOR data type. This function can be used with local or global cursors and determines whether or not the stored procedure has returned a cursor with a result set. The syntax is:
1.CURSOR_STATUS( 'local' or 'global', cursor name)or
1.CURSOR_STATUS ('variable', cursor variable name)LOCAL or GLOBAL keywords allow you to specify the cursor scope; VARIABLE keyword specifies that CURSOR_STATUS function should examine a cursor variable.
In order to use the CURSOR data type as an output parameter, you must specify VARYING keyword along with OUTPUT within the CREATE PROCEDURE statement. The following example creates a procedure that returns a cursor as an output parameter:
01.CREATE PROCEDURE RETURN_CUSTOMER_NAMES(02.@last_name VARCHAR(50),03.@my_cursor CURSOR VARYING OUTPUT)04.AS05.BEGIN06.SET NOCOUNT ON07. 08.SET @my_cursor = CURSOR STATIC FOR SELECT LastName, FirstName FROM DimCustomerWHERE LastName = @last_name09. 10.OPEN @my_cursor11.ENDNext, you can call this procedure from another procedure with a particular last name. The CURSOR_STATUS function will let you determine whether the cursor returned from the first procedure contains any rows, as follows:
01.CREATE PROCEDURE CALL_THE_OTHER_PROCEDURE02.@last_name VARCHAR(50)03.AS04.SET NOCOUNT ON05. 06.DECLARE @first_name VARCHAR(20)07. 08.DECLARE @my_cursor CURSOR09. 10./* now call the procedure returning a cursor output parameter */11.EXECUTE RETURN_CUSTOMER_NAMES12.@last_name ,13.@my_cursor OUTPUT14. 15.IF CURSOR_STATUS('variable','@my_cursor') = 016.BEGIN17.PRINT 'no records found'18. 19.RETURN20.END21.ELSE22.BEGIN23.FETCH NEXT FROM @my_cursor24.INTO @last_name,25.@first_name26. 27.WHILE @@FETCH_STATUS = 028.BEGIN29.SELECT @last_name,30.@first_name31. 32.FETCH NEXT FROM @my_cursor33.INTO @last_name,34.@first_name35.END36. 37.CLOSE @my_cursor38. 39.DEALLOCATE @my_cursor40.ENDOnce the two procedures are created you can execute the caller procedure with the last name "Huang" as follows:
1.EXECUTE call_the_other_procedure 'huang'Results (abbreviated):
1.-------------------------------------------------- -------------------- 2.Huang Eugene 3.-------------------------------------------------- -------------------- 4.Huang Erica 5.-------------------------------------------------- -------------------- 6.Huang Alejandro 7.-------------------------------------------------- -------------------- 8.Huang ShannonThe following table summarizes the values returned by CURSOR_STATUS function:
| CURSOR_STATUS value | Meaning for variable | Meaning for cursor name |
|---|---|---|
| 1 | Cursor is open and has at least 1 row DYNAMIC cursors return 1 even if they contain no rows | Cursor is open and has at least 1 row DYNAMIC cursors return 1 even if they contain no rows |
| 0 | Cursor is open but has no rows | Cursor has no rows |
| -1 | The cursor is closed | The cursor is closed |
| -2 | Cursor wasn't returned by the called procedure OR the cursor was deallocated prior to being assigned to this variable | The value of 2 is never returned by CURSOR_STATUS if you refer to cursor name |
| -3 | Cursor variable with the specified name does not exist OR the variable exists, but it hasn't been assigned to a cursor returned from the called procedure | Cursor with the specified name does not exist |
The following example shows how the values returned by the CURSOR_STATUS function change during the cursor's lifecycle:
01.SET NOCOUNT ON 02. 03.SELECT 'cursor hasn''t been declared; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)04. 05.DECLARE @last_name VARCHAR(20),06.@first_name VARCHAR(20)07. 08.DECLARE MY_CURSOR CURSOR GLOBAL FOR09.SELECT LASTNAME,10.FIRSTNAME11.FROM DIMCUSTOMER12.WHERE LASTNAME = 'white'13. 14.SELECT 'cursor is declared; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)15. 16.OPEN MY_CURSOR17. 18.SELECT 'cursor is opened; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)19. 20.FETCH NEXT FROM MY_CURSOR21.INTO @last_name,22.@first_name23. 24.WHILE @@FETCH_STATUS = 025.BEGIN26.SELECT @last_name,27.@first_name28. 29.FETCH NEXT FROM MY_CURSOR30.INTO @last_name,31.@first_name32.END33. 34.CLOSE MY_CURSOR35. 36.SELECT 'cursor is closed; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)37. 38.DEALLOCATE MY_CURSOR39. 40.SELECT 'cursor is deallocated; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)Results (abbreviated):
01.-------------------------------------------------------------------------- 02.cursor hasn't been declared; the status is: -3 03.----------------------------------------------------------------- 04.cursor is declared; the status is: -1 05.--------------------------------------------------------------- 06.cursor is opened; the status is: 1 07.-------------------- -------------------- 08.WhiteIan 09.--------------------------------------------------------------- 10.cursor is closed; the status is: -1 11.-------------------------------------------------------------------- 12.cursor is deallocated; the status is: -3
No comments:
Post a Comment