In the following example, procedure p1creates a cursor defined as global that can then be accessed by procedure p2: if object_id‘p1’ is not null drop proc p1 go if object_id‘p2’ is not n
Trang 1If stored procedures are nested, they can access cursors declared in higher-level stored
procedures in the call tree, but only if the cursors are declared as global If the cursor is
declared as local, it can be referenced only within the scope of the stored procedure in
which it is declared It cannot be accessed from a called or calling procedure If the cursor
is declared as global, it can be accessed within the declaring procedure, in a called
proce-dure, or even from outside the declaring procedure if the cursor is not deallocated before
the procedure returns.
In the following example, procedure p1creates a cursor defined as global that can then be
accessed by procedure p2:
if object_id(‘p1’) is not null
drop proc p1
go
if object_id(‘p2’) is not null
drop proc p2
go
create proc p2
as
set nocount on
fetch from global cursor defined in calling proc p1
fetch c1
return
go
create proc p1
as
set nocount on
Declare global cursor
declare c1 cursor global for
select title_id, type from titles
open c1
fetch c1
exec p2
close c1
deallocate c1
go
exec p1
go
title_id type
-
-BI0194 biography
Trang 2title_id type
-
-BI1408 biography
As you can see in the preceding example, the cursor c1is defined as global in procedure
p1and can be accessed from within procedure p2.
TIP
To clean up the output when using cursors within stored procedures, specify the set
nocount onoption within the stored procedure to disable the n rows(s) affected
that would normally be displayed after each invocation of the fetchstatement.
Now, look what happens if you modify procedure p1to declare the cursor as local:
alter proc p1
as
set nocount on
Declare local cursor
declare c1 cursor local for
select title_id, type from titles
open c1
fetch c1
exec p2
close c1
deallocate c1
go
exec p1
go
title_id type
-
-BI0194 biography
Msg 16916, Level 16, State 1, Procedure p2, Line 5
A cursor with the name ‘c1’ does not exist
Notice in this example that the cursor c1is not available to the procedure p2 The reason
is that the cursor is defined as local and is accessible only within the scope of procedure
p1 Because the cursor is localized to the scope of p1, you are able to define a cursor with
the same name within the scope of procedure p2:
alter proc p2
as
set nocount on
Trang 3Declare another local cursor with same name ‘c1’
declare c1 cursor local for
select au_id, au_lname from authors
open c1
fetch c1
close c1
deallocate c1
return
go
exec p1
go
title_id type
-
-BI0194 biography
au_id au_lname
-
-047-43-0360 Michener
Notice that when you define the scope of both cursors as local, each procedure can create
a cursor with the same name without any conflict between them You can take advantage
of this feature if you have a recursive stored procedure that uses a cursor, as demonstrated
in the “Recursive Stored Procedures” section, later in this chapter.
In addition to a global cursor defined in a calling procedure being available within a called
procedure, the reverse is possible as well A global cursor defined in a called procedure is
available to the calling procedure as demonstrated in the following example:
alter proc p2
as
set nocount on
declare global cursor c2
declare c2 cursor global for
select au_id, au_lname from authors
open c2
do not close/deallocate cursor so it can be used by calling proc p1
return
go
alter proc p1
as
set nocount on
declare c1 cursor local for
select title_id, type from titles
open c1
fetch c1
exec p2
Trang 4fetch from global cursor declared in proc p2
fetch c2
close c1
deallocate c1
close c2
deallocate c2
return
go
exec p1
go
title_id type
-
-BI0194 biography
au_id au_lname
-
-047-43-0360 Michener
As you can see in the preceding example, the global cursor defined in the called procedure
p2is available for use by the calling procedure p1as long as the cursor is left open by the
called procedure p2.
NOTE
Remember that global cursors persist beyond the scope of the procedure in which they
are defined If you are going to declare global cursors in called procedures to be
accessed by the calling procedure, be sure the calling procedure closes and
deallo-cates the cursor declared in the called procedure before it returns Otherwise, the
cursor will remain open and defined until the end of the user session The following
example demonstrates this behavior:
alter proc p1
as
set nocount on
declare c1 cursor local for
select title_id, type from titles
open c1
fetch c1
exec p2
fetch from global cursor declared in proc p2
fetch c2
close c1
deallocate c1
Trang 5Cursor c2 is not closed/deallocated before return
return
go
exec p1
go
Cursor c2 should still be open here so the following fetch will work
fetch c2
go
title_id type
-
-BI0194 biography
au_id au_lname
-
-047-43-0360 Michener
au_id au_lname
-
-052-04-3539 Gray
Using CURSOR Variables in Stored Procedures
Another method available in SQL Server 2008 for passing cursor result sets between stored
procedures is using the cursor data type The cursor data type can be used to bind a cursor
result set to a local variable, and that variable can then be used to manage and access the
cursor result set Cursor variables can be referenced in any of the cursor management
statements:OPEN,FETCH,CLOSE, and DEALLOCATE.
A stored procedure can pass cursor variables as output parameters only; cursor variables
cannot be passed as input parameters When defining a CURSORoutput parameter, you
must also specify the VARYINGkeyword.
When assigning a cursor to a cursor variable, you must use the SETcommand because an
assignmentselectis not allowed Cursor data types can either be the source or the target
in a SETstatement.
The following stored procedure declares a cursor, opens it, and passes it back as an output
parameter using the cursor data type:
IF EXISTS ( SELECT * FROM sys.procedures
WHERE schema_id = schema_id(‘dbo’)
Trang 6AND name = N’cursor_proc’) DROP PROCEDURE dbo.cursor_proc
GO
create proc cursor_proc @cursor CURSOR VARYING OUTPUT
as
declare curs1 cursor global for
select cast(title as varchar(30)) as title , pubdate from titles
set @cursor = curs1
open curs1
return
A cursor variable and the declared cursor name can be used interchangeably in cursor
commands You can use either the variable name or declared name to open, fetch, close,
and deallocate the cursor Fetching using either the cursor name or cursor variable fetches
the next row in the cursor result set Listing 44.6 illustrates how each fetch gets the next
row in the result set.
LISTING 44.6 Fetching Cursor Rows by Using the Declared Cursor Name and a Cursor
Variable
set nocount on
declare @curs CURSOR
exec cursor_proc @cursor = @curs output
fetch curs1
fetch @curs
fetch curs1
fetch @curs
go
title pubdate
-Samuel Johnson 2008-09-19 00:00:00.000
title pubdate
-Freud, Dora, and Vienna 1900 2008-02-25 00:00:00.000
title pubdate
-Freud: A Life for Our Time 2008-06-21 00:00:00.000
title pubdate
-For Love of the World 2006-01-06 00:00:00.000
Trang 7One of the problems with a cursor declared as a global cursor in the procedure is that you
cannot invoke the procedure again within the same session unless the cursor is closed and
deallocated This can be a problem if you need to get the cursor into a cursor variable
again If you try to invoke the procedure again, and the cursor hasn’t been closed or
deal-located, you get error messages, as shown in the following example:
set nocount on
declare @curs CURSOR
exec cursor_proc @cursor = @curs output
go
Msg 16915, Level 16, State 1, Procedure cursor_proc, Line 4
A cursor with the name ‘curs1’ already exists
Msg 16905, Level 16, State 1, Procedure cursor_proc, Line 6
The cursor is already open
close curs1
deallocate curs1
go
One way to work around this issue is to use theCURSOR_STATUSfunction in the procedure
to check whether the cursor exists yet before declaring it and also to check whether the
cursor is already open before opening it Thus, the stored procedure declares the cursor
only if it doesn’t exist and opens the cursor only if it’s closed, but the stored procedure
always returns the cursor in the cursor output parameter Keeping this in mind, take a
look at a revised version of the cursor_procstored procedure:
IF EXISTS ( SELECT * FROM sys.procedures
WHERE schema_id = schema_id(‘dbo’) AND name = N’cursor_proc’) DROP PROCEDURE dbo.cursor_proc
GO
go
create proc cursor_proc @cursor CURSOR VARYING OUTPUT
as
if CURSOR_STATUS(‘global’, ‘curs1’) = -3 cursor does not exist
declare curs1 cursor global for
select cast(title as varchar(30)) as title , pubdate from titles
if CURSOR_STATUS(‘global’, ‘curs1’) = -1 cursor is not open
open curs1
set @cursor = curs1
return
When the procedure is written this way, you can now safely call the procedure at any
time, even if the cursor is already open If the cursor is open, it simply stores the cursor
pointer into the cursor variable.
Trang 8If you want to close the cursor, you can do so by using either the cursor variable or
declared cursor name When it is closed, however, you cannot fetch more rows from the
cursor or cursor variable until it is reopened:
set nocount on
declare @curs CURSOR
exec cursor_proc @cursor = @curs output
fetch curs1
fetch @curs
close the cursor
close curs1
try to fetch from the cursor variable
fetch @curs
go
title pubdate
-Samuel Johnson 2008-09-19 00:00:00.000
title pubdate
-Freud, Dora, and Vienna 1900 2008-02-25 00:00:00.000
Msg 16917, Level 16, State 2, Line 7
Cursor is not open
However, if the cursor has been assigned to a cursor variable, it cannot be fully deallocated
until the last remaining reference to the cursor issues the DEALLOCATEcommand Until all
references to the cursor issue the DEALLOCATEcommand, the cursor can be reopened, but
only by using the remaining cursor reference(s) that hasn’t issued the DEALLOCATE
command An example of this behavior is shown in Listing 44.7 If the cursor has not
been closed, only the last deallocation of the cursor closes it.
LISTING 44.7 Deallocating a Cursor by Cursor Name and Cursor Variable
declare @curs CURSOR
exec cursor_proc @cursor = @curs output
print ‘FETCH VIA NAME:’
fetch curs1
print ‘FETCH VIA VARIABLE:’
fetch @curs
print ‘CLOSE BY NAME’
close curs1
print ‘DEALLOCATE BY NAME’
Trang 9deallocate curs1
print ‘ATTEMPT FETCH VIA VARIABLE (CURSOR SHOULD BE CLOSED):’
fetch @curs
print ‘ATTEMPT TO OPEN VIA VARIABLE (CURSOR SHOULD OPEN, NOT DEALLOCATED YET)’
open @curs
print ‘ATTEMPT FETCH VIA VARIABLE (SHOULD START FROM BEGINNING AGAIN):’
fetch @curs
print ‘CLOSE AND DEALLOCATE VIA VARIABLE’
close @curs
deallocate @curs
print ‘ATTEMPT TO OPEN VIA VARIABLE (SHOULD FAIL, SINCE NOW FULLY DEALLOCATED):’
open @curs
go
FETCH VIA NAME:
TITLE PUBDATE
-SAMUEL JOHNSON 2008-09-19 00:00:00.000
FETCH VIA VARIABLE:
TITLE PUBDATE
-FREUD, DORA, AND VIENNA 1900 2008-02-25 00:00:00.000
CLOSE BY NAME
DEALLOCATE BY NAME
ATTEMPT FETCH VIA VARIABLE (CURSOR SHOULD BE CLOSED):
MSG 16917, LEVEL 16, STATE 2, LINE 15
CURSOR IS NOT OPEN
ATTEMPT TO OPEN VIA VARIABLE (CURSOR SHOULD OPEN, NOT DEALLOCATED YET)
ATTEMPT FETCH VIA VARIABLE (SHOULD START FROM BEGINNING AGAIN):
TITLE PUBDATE
-SAMUEL JOHNSON 2008-09-19 00:00:00.000
CLOSE AND DEALLOCATE VIA VARIABLE
ATTEMPT TO OPEN VIA VARIABLE (SHOULD FAIL, SINCE NOW FULLY DEALLOCATED):
MSG 16950, LEVEL 16, STATE 2, LINE 27
The variable ‘@curs’ does not currently have a cursor allocated to it
Trang 10If the cursor is declared as a local cursor within a stored procedure, it can still be passed
back in an output variable to a cursor variable, but it is accessible only through the cursor
variable, as shown in Listing 44.8.
LISTING 44.8 Assigning a Local Cursor to a Cursor Output Parameter
IF EXISTS ( SELECT * FROM sys.procedures
WHERE schema_id = schema_id(‘dbo’) AND name = N’cursor_proc2’) DROP PROCEDURE dbo.cursor_proc2
GO
create proc cursor_proc2 @cursor CURSOR varying output
as
declare curs1 cursor local for
select cast(title as varchar(30)) as title , pubdate from titles
set @cursor = curs1
open curs1
go
declare @curs CURSOR
exec cursor_proc2 @cursor = @curs output
print ‘ATTEMPT FETCH VIA NAME:’
fetch next from curs1
print ‘ATTEMPT FETCH VIA VARIABLE:’
fetch next from @curs
go
ATTEMPT FETCH VIA NAME:
Msg 16916, Level 16, State 1, Line 4
A cursor with the name ‘curs1’ does not exist
ATTEMPT FETCH VIA VARIABLE:
title pubdate
-Samuel Johnson 2008-09-19 00:00:00.000
Nested Stored Procedures
Stored procedures can call other stored procedures, and any of those procedures can call
other procedures, up to a maximum nesting level of 32 levels deep If you exceed the
32-level nesting limit, an error message is raised, the batch is aborted, and any open
transac-tion in the session is rolled back The nesting level limit prevents a recursive procedure
from calling itself repeatedly in an infinite loop until a stack overflow occurs To check the
depth to which a procedure is nested, you use the system function @@NESTLEVEL(see
Listing 44.9).