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_name
05.
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_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 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
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 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
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
:
-
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