Friday, 19 August 2011

Built-in Functions - Cursor Functions

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 valueMeaning
0Successful fetch of a row within a cursor
-1Fetch 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.
-2The 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_name
05.WHILE @@FETCH_STATUS =
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_cursor
Results:
01.fetch_status 
02.------------ 
03.0
04.fetch_status 
05.------------ 
06.0
07.fetch_status 
08.------------ 
09.0
10.fetch_status 
11.------------ 
12.0
13.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 valueMeaning
-mCursor is being populated asynchronously. "M" is the value of records in the record set.
-1The 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:
  • Cursor has not been opened
  • Cursor has no rows
  • Cursor has been closed
NNumber 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  FOR
05.SELECT TOP 3 LASTNAME,
06.FIRSTNAME
07.FROM   DIMCUSTOMER
08. 
09.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'
10. 
11.OPEN MY_CURSOR
12. 
13.FETCH NEXT FROM MY_CURSOR
14.INTO @last_name,
15.@first_name
16. 
17.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'
18. 
19.WHILE @@FETCH_STATUS = 0
20.BEGIN-- typically you'd do some row-based operation here 
21.FETCH NEXT FROM MY_CURSOR
22.INTO @last_name,
23.@first_name
24.END
25. 
26.CLOSE MY_CURSOR
27. 
28.SELECT 'cursor has ' + CAST(@@CURSOR_ROWS AS VARCHAR) + ' rows'
29. 
30.DEALLOCATE MY_CURSOR
Results:
1.---------------------------------------------- 
2.cursor has 0 rows 
3.---------------------------------------------- 
4.cursor has 3 rows 
5.---------------------------------------------- 
6.cursor has 0 rows
If 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.AS
05.BEGIN
06.SET NOCOUNT ON
07. 
08.SET @my_cursor =   CURSOR STATIC FOR   SELECT LastName, FirstName FROM DimCustomerWHERE LastName = @last_name
09. 
10.OPEN @my_cursor
11.END
Next, 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_PROCEDURE
02.@last_name VARCHAR(50)
03.AS
04.SET NOCOUNT ON
05. 
06.DECLARE  @first_name VARCHAR(20)
07. 
08.DECLARE  @my_cursor CURSOR
09. 
10./* now call the procedure returning a cursor output parameter */
11.EXECUTE RETURN_CUSTOMER_NAMES
12.@last_name ,
13.@my_cursor OUTPUT
14. 
15.IF CURSOR_STATUS('variable','@my_cursor') = 0
16.BEGIN
17.PRINT 'no records found'
18. 
19.RETURN
20.END
21.ELSE
22.BEGIN
23.FETCH NEXT FROM @my_cursor
24.INTO @last_name,
25.@first_name
26. 
27.WHILE @@FETCH_STATUS = 0
28.BEGIN
29.SELECT @last_name,
30.@first_name
31. 
32.FETCH NEXT FROM @my_cursor
33.INTO @last_name,
34.@first_name
35.END
36. 
37.CLOSE @my_cursor
38. 
39.DEALLOCATE @my_cursor
40.END
Once 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  Shannon
The following table summarizes the values returned by CURSOR_STATUS function:
CURSOR_STATUS valueMeaning for variableMeaning 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
0Cursor is open but has no rowsCursor has no rows
-1The cursor is closed The cursor is closed
-2Cursor wasn't returned by the called procedure OR the cursor was deallocated prior to being assigned to this variableThe value of 2 is never returned by CURSOR_STATUS if you refer to cursor name
-3Cursor 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 procedureCursor 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 FOR
09.SELECT LASTNAME,
10.FIRSTNAME
11.FROM   DIMCUSTOMER
12.WHERE  LASTNAME = 'white'
13. 
14.SELECT 'cursor is declared; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)
15. 
16.OPEN MY_CURSOR
17. 
18.SELECT 'cursor is opened; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)
19. 
20.FETCH NEXT FROM MY_CURSOR
21.INTO @last_name,
22.@first_name
23. 
24.WHILE @@FETCH_STATUS = 0
25.BEGIN
26.SELECT @last_name,
27.@first_name
28. 
29.FETCH NEXT FROM MY_CURSOR
30.INTO @last_name,
31.@first_name
32.END
33. 
34.CLOSE MY_CURSOR
35. 
36.SELECT 'cursor is closed; the status is: ' + CAST(CURSOR_STATUS('global','my_cursor') AS VARCHAR)
37. 
38.DEALLOCATE MY_CURSOR
39. 
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: -
03.----------------------------------------------------------------- 
04.cursor is declared; the status is: -
05.--------------------------------------------------------------- 
06.cursor is opened; the status is: 1
07.-------------------- -------------------- 
08.WhiteIan 
09.--------------------------------------------------------------- 
10.cursor is closed; the status is: -
11.-------------------------------------------------------------------- 
12.cursor is deallocated; the status is: -3

No comments:

Post a Comment