Cursor

1.   Syntax 

DECLARE cursor_name CURSOR [LOCAL | GLOBAL ]

     [ FORWARD_ONLY | SCROLL ]

     [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]

     [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]

     [ TYPE_WARNING ]

     FOR select_statement

     [ FOR UPDATE [ OF column_name [ ,...n ] ] ]

[;] 

 

2.   Where can it be used?

1)    Ad Hoc Batches

2)    SPs

3)    Triggers 

 

3.   When should it be used? – A short answer: you probably should not use it unless you are a database admin

1)    Some administrative tasks such as rebuild each index by looping through the cursor

2)    You need to perform different operation on different rows in a table based on criteria that are more complex than can be stated in a WHERE clause.

 

Most of the cursors can be re-written to be as a set-based operation. See below for examples.

 

http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them 

 

4.   What is the basic procedure to use a cursor?

1)    Declare a cursor that defines a result set.

2)    Open the cursor to establish the result set

3)    Fetch the data into local variables as needed from the cursor, one row at a time

4)    Close the cursor when done

5)    Deallocate the cursor to release the resource

5.   De-mystifying the options for a cursor with examples 

1)             LOCAL vs. GLOBAL: specify the scope of the cursor. Default to GLOBAL (a DB option).

 

LOCAL

Specifies that the scope of the cursor is local to the batch, stored procedure, or trigger in which the cursor was created. The cursor name is only valid within this scope. The cursor can be referenced by local cursor variables in the batch, stored procedure, or trigger, or a stored procedure OUTPUT parameter. An OUTPUT parameter is used to pass the local cursor back to the calling batch, stored procedure, or trigger, which can assign the parameter to a cursor variable to reference the cursor after the stored procedure terminates. The cursor is implicitly deallocated when the batch, stored procedure, or trigger terminates, unless the cursor was passed back in an OUTPUT parameter. If it is passed back in an OUTPUT parameter, the cursor is deallocated when the last variable referencing it is deallocated or goes out of scope.

 

GLOBAL

Specifies that the scope of the cursor is global to the connection. The cursor name can be referenced in any stored procedure or batch executed by the connection. The cursor is only implicitly deallocated at disconnect.

 

If neither GLOBAL nor LOCAL is specified, the default is controlled by the setting of the default to local cursor database option, which is GLOBAL by default.

 

2)             FORWARD_ONLY vs. SCROLL: specify the direction of the cursor. Default to Forward_Only or Fetch Next

 

FORWARD_ONLY – from the 1st to the last row (but it is NOT read_only!)

 

Specifies that the cursor can only be scrolled from the first to the last row. FETCH NEXT is the only supported fetch option. If FORWARD_ONLY is specified without the STATIC, KEYSET, or DYNAMIC keywords, the cursor operates as a DYNAMIC cursor. When neither FORWARD_ONLY nor SCROLL is specified, FORWARD_ONLY is the default, unless the keywords STATIC, KEYSET, or DYNAMIC are specified. STATIC, KEYSET, and DYNAMIC cursors default to SCROLL. Unlike database APIs such as ODBC and ADO, FORWARD_ONLY is supported with STATIC, KEYSET, and DYNAMIC Transact-SQL cursors.

 

Forward Only Cursor – Example

 

/*First, prepare the table data. The example is from the link below. The same table is also used for other examples of cursor unless specified.

http://www.dotnet-tricks.com/Tutorial/sqlserver/RLID060512-SQL-Server-Different-Types-of-Cursors.html

*/

Examples:

CREATE TABLE Employee

    (

      EmpID INT PRIMARY KEY ,

      EmpName VARCHAR(50) NOT NULL ,

      Salary INT NOT NULL ,

      Address VARCHAR(200) NOT NULL,

    )

GO

 

INSERT  INTO Employee

        ( EmpID, EmpName, Salary, Address )

VALUES  ( 1, 'Mohan', 12000, 'Noida' )

 

INSERT  INTO Employee

        ( EmpID, EmpName, Salary, Address )

VALUES  ( 2, 'Pavan', 25000, 'Delhi' )

 

INSERT  INTO Employee

        ( EmpID, EmpName, Salary, Address )

VALUES  ( 3, 'Amit', 22000, 'Dehradun' )

 

INSERT  INTO Employee

        ( EmpID, EmpName, Salary, Address )

VALUES  ( 4, 'Sonu', 22000, 'Noida' )

 

INSERT  INTO Employee

        ( EmpID, EmpName, Salary, Address )

VALUES  ( 5, 'Deepak', 28000, 'Gurgaon' )

GO

 

SELECT  *

FROM    Employee

 

-- Note: If FORWARD_ONLY is specified without the STATIC, KEYSET, or DYNAMIC keywords, the cursor

-- operates as a DYNAMIC cursor.

 

--Forward Only Cursor for Update

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Forward_cur_empupdate CURSOR FORWARD_ONLY

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Forward_cur_empupdate

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Forward_cur_empupdate INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Amit'

                    UPDATE  Employee

                    SET     Salary = 24000

                    WHERE CURRENT OF Forward_cur_empupdate

                FETCH NEXT FROM Forward_cur_empupdate INTO @Id, @name

            END

    END

 

CLOSE Forward_cur_empupdate

 

DEALLOCATE Forward_cur_empupdate

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

 

-- Forward Only Cursor for Delete

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Forward_cur_empdelete CURSOR FORWARD_ONLY

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Forward_cur_empdelete

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Forward_cur_empdelete INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Sonu'

                    DELETE  Employee

                    WHERE CURRENT OF Forward_cur_empdelete

                FETCH NEXT FROM Forward_cur_empdelete INTO @Id, @name

            END

    END

 

CLOSE Forward_cur_empdelete

 

DEALLOCATE Forward_cur_empdelete

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

 

SCROLL – starting from anywhere, relative or absolute

 

Specifies that all fetch options (FIRST, LAST, PRIOR, NEXT, RELATIVE, ABSOLUTE) are available. If SCROLL is not specified in an ISO DECLARE CURSOR, NEXT is the only fetch option supported. SCROLL cannot be specified if FAST_FORWARD is also specified.

 

The target position for a scrollable cursor can be specified relatively (from the current cursor position) or absolutely (from the beginning of the result set).

 

 FETCH [NEXT|PRIOR|FIRST|LAST] FROM cursor_name

 FETCH ABSOLUTE n FROM cursor_name

 FETCH RELATIVE n FROM cursor_name

 

3)             STATIC/ FAST_FORWARD /KEYSET/DYNAMIC: specify the type of a cursor in terms of the availability of the changes in the underlying table visible to the cursor. Default to FAST_FORWARD

 

STATIC  When the cursor is opened, it makes a temporary copy of the data to be used by the cursor. Thus, it is a read-only cursor, supporting both forward and backward reads on the temp table. The underlying table can be modified but not reflected in the cursor table.

 

Defines a cursor that makes a temporary copy of the data to be used by the cursor. All requests to the cursor are answered from this temporary table in tempdb; therefore, modifications made to base tables are not reflected in the data returned by fetches made to this cursor, and this cursor does not allow modifications.

 

The complete result set of a static cursor is built in tempdb when the cursor is opened. A static cursor always displays the result set as it was when the cursor was opened. Static cursors detect few or no changes, but consume relatively few resources while scrolling.

 

The cursor does not reflect any changes made in the database that affect either the membership of the result set or changes to the values in the columns of the rows that make up the result set. A static cursor does not display new rows inserted in the database after the cursor was opened, even if they match the search conditions of the cursor SELECT statement. If rows making up the result set are updated by other users, the new data values are not displayed in the static cursor. The static cursor displays rows deleted from the database after the cursor was opened. No UPDATE, INSERT, or DELETE operations are reflected in a static cursor (unless the cursor is closed and reopened), not even modifications made using the same connection that opened the cursor.

 

SQL Server static cursors are always read-only.

Because the result set of a static cursor is stored in a work table in tempdb, the size of the rows in the result set cannot exceed the maximum row size for a SQL Server table.

Transact-SQL uses the term insensitive for static cursors. Some database APIs identify them as snapshot cursors. (from http://msdn.microsoft.com/en-us/library/ms191179.aspx)

 

==From Grant Fritchey’s Book SQL SERVER 2012 QUERY PERFORMANCE TUNING ==

 

These are the characteristics of static cursors:

·       They create a snapshot of cursor results in the tempdb database when the cursor is opened. Thereafter, static cursors operate on the snapshot in the tempdb database.

·       Data is retrieved from the underlying table(s) when the cursor is opened.

·       Static cursors support all scrolling options: FETCH FIRST, FETCH NEXT, FETCH PRIOR, FETCH LAST, FETCH ABSOLUTE n, and FETCH RELATIVE n.

·       Static cursors are always read-only; data modifications are not allowed through static cursors. Also, changes (INSERT, UPDATE, and DELETE) made to the underlying table(s) are not reflected in the cursor.

·       Some tests show that a static cursor can perform as well as—and sometimes faster than—a forward-only cursor. Be sure to test this behavior on your own system.

 

--Static Cursor – Example using the above table data

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

DECLARE @salary INT

 

DECLARE cur_emp CURSOR STATIC

FOR

    SELECT  EmpID ,

            EmpName ,

            Salary

    FROM    Employee

 

OPEN cur_emp

 

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM cur_emp INTO @Id, @name, @salary

        WHILE @@Fetch_status = 0

            BEGIN

                PRINT 'ID : ' + CONVERT(VARCHAR(20), @Id) + ', Name : '

                    + @name + ', Salary : ' + CONVERT(VARCHAR(20), @salary)

                FETCH NEXT FROM cur_emp INTO @Id, @name, @salary

            END

    END

 

CLOSE cur_emp

 

DEALLOCATE cur_emp

 

 

SET NOCOUNT OFF

 

FAST_FORWARD – forward ready only. But the rows are not retrieved until they are fetched. Thus, the changes made to the underlying table are visible to the fetch.

 

Specifies a FORWARD_ONLY, READ_ONLY cursor with performance optimizations enabled (What does it mean? How is a FAST_FORWARD cursor different from a FORWARD_ONLY, READ_ONLY cursor? See http://blogs.msdn.com/b/sqlqueryprocessing/archive/2009/08/12/understanding-sql-server-fast-forward-server-cursors.aspx for details)

 

FAST_FORWARD cannot be specified if SCROLL or FOR_UPDATE is also specified.

 

Note: In SQL Server 2005 and later, both FAST_FORWARD and FORWARD_ONLY can be used in the same DECLARE CURSOR statement.

 

A forward-only cursor does not support scrolling; it supports only fetching the rows serially from the start to the end of the cursor. The rows are not retrieved from the database until they are fetched. The effects of all INSERT, UPDATE, and DELETE statements made by the current user or committed by other users that affect rows in the result set are visible as the rows are fetched from the cursor.

 

Because the cursor cannot be scrolled backward, most changes made to rows in the database after the row was fetched are not visible through the cursor. In cases where a value used to determine the location of the row within the result set is modified, such as updating a column covered by a clustered index, the modified value is visible through the cursor (http://msdn.microsoft.com/en-us/library/ms191179.aspx)

 

==from Grant Fritchey’s Book==

 

These are the characteristics of forward-only (aka fast_forward) cursors:

·       They operate directly on the base table(s).

·       Rows from the underlying table(s) are usually not retrieved until the cursor rows are fetched using the cursor FETCH operation. However, the database API forward-only cursor type, with the following additional characteristics, retrieves all the rows from the underlying table first:

·       Client-side cursor location

·       Server-side cursor location and read-only cursor concurrency

·       They support forward scrolling only (FETCH NEXT) through the cursor.

·       They allow all changes (INSERT, UPDATE, and DELETE) through the cursor. Also, these cursors reflect all changes made to the underlying table(s).

·       A forward-only cursor with a read-only property can be created using a fast forward statement. The T-SQL syntax provides a specific cursor type option, FAST_FORWARD, to create a fast-forward-only cursor. The nickname for the FAST_FORWARD cursor is the fire hose because it is the fastest way to move data through a cursor and because all the information flows one way. However, don’t be surprised when the “firehose” is still not as fast as traditional set-based operations.

 

--A code snippet from Grant’s bool

DECLARE MyCursor CURSOR FAST_FORWARD

FOR

SELECT  adt.Name

FROM   Person.AddressType AS adt

WHERE  adt.AddressTypeID = 1 ;

 

KEYSETstores only the primary keys in the background temporary table and in every row-fetch it queries the original data based on the current key. It maintains a fixed order of rows in the tempdb database.

 

Specifies that the membership and order of rows in the cursor are fixed when the cursor is opened. The set of keys that uniquely identify the rows is built into a table in tempdb known as the keyset.

 

Note: If the query references at least one table without a unique index, the keyset cursor is converted to a static cursor.

 

Changes to nonkey values in the base tables, either made by the cursor owner or committed by other users, are visible as the owner scrolls around the cursor. Inserts made by other users are not visible (inserts cannot be made through a Transact-SQL server cursor). If a row is deleted, an attempt to fetch the row returns an @@FETCH_STATUS of -2. Updates of key values from outside the cursor resemble a delete of the old row followed by an insert of the new row. The row with the new values is not visible, and attempts to fetch the row with the old values return an @@FETCH_STATUS of -2. The new values are visible if the update is done through the cursor by specifying the WHERE CURRENT OF clause.

 

In other words, a keyset driven cursor is controlled by a set of unique identifiers as the keys in the keyset. The keyset depends on all the rows that qualified the SELECT statement at the time of cursor was opened. A keyset driven cursor is sensitive to any changes to the data source and supports update, delete operations. By default keyset driven cursors are scrollable (http://blogs.msdn.com/b/sqlqueryprocessing/archive/2009/08/12/understanding-sql-server-fast-forward-server-cursors.aspx)

 

==from Grant Fritchey’s Book==

 

These are the characteristics of keyset-driven cursors:

·       Keyset cursors are controlled by a set of unique identifiers (or keys) known as a keyset.

·       The keyset is built from a set of columns that uniquely identify the rows in the result set.

·       These cursors create the keyset of rows in the tempdb database when the cursor is opened.

·       Membership of rows in the cursor is limited to the keyset of rows created in the tempdb database when the cursor is opened.

·       On fetching a cursor row, the database engine first looks at the keyset of rows in tempdb, and then navigates to the corresponding data row in the underlying table(s) to retrieve the remaining columns.

·       They support all scrolling options.

·       Keyset cursors allow all changes through the cursor.

 

An INSERT performed outside the cursor is not reflected in the cursor, since the membership of rows in the cursor is limited to the keyset of rows created in the tempdb database on opening the cursor. An INSERT through the cursor appears at the end of the cursor. A DELETE performed on the underlying table(s) raises an error when the cursor navigation reaches the deleted row. An UPDATE on the nonkeyset columns of the underlying table(s) is reflected in the cursor.

 

An UPDATE on the keyset column(s) is treated like a DELETE of an old key value and the INSERT of a new key value. If a change disqualifies a row for membership or affects the order of a row, then the row does not disappear or move unless the cursor is closed and reopened.

 

Keyset Driven Cursor - Example

-- Keyset driven Cursor for Update

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Keyset_cur_empupdate CURSOR KEYSET

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Keyset_cur_empupdate

 

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Keyset_cur_empupdate INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Pavan'

                    UPDATE  Employee

                    SET     Salary = 27000

                    WHERE CURRENT OF Keyset_cur_empupdate

                FETCH NEXT FROM Keyset_cur_empupdate INTO @Id, @name

            END

    END

 

CLOSE Keyset_cur_empupdate

 

DEALLOCATE Keyset_cur_empupdate

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

-- Keyse Driven Cursor for Delete

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Keyset_cur_empdelete CURSOR KEYSET

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Keyset_cur_empdelete

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Keyset_cur_empdelete INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Amit'

                    DELETE  Employee

                    WHERE CURRENT OF Keyset_cur_empdelete

                FETCH NEXT FROM Keyset_cur_empdelete INTO @Id, @name

            END

    END

 

CLOSE Keyset_cur_empdelete

DEALLOCATE Keyset_cur_empdelete

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

 

 

DYNAMIC – all changes made to the underlying table is reflected in the fetch result of the cursor

 

Defines a cursor that reflects all data changes made to the rows in its result set as you scroll around the cursor. The data values, order, and membership of the rows can change on each fetch. The ABSOLUTE fetch option is not supported with dynamic cursors.

 

Dynamic cursors are the opposite of static cursors. Dynamic cursors reflect all changes made to the rows in their result set when scrolling through the cursor. The data values, order, and membership of the rows in the result set can change on each fetch. All UPDATE, INSERT, and DELETE statements made by all users are visible through the cursor. Updates are visible immediately if they are made through the cursor using either an API function such as SQLSetPos or the Transact-SQL WHERE CURRENT OF clause. Updates made outside the cursor are not visible until they are committed, unless the cursor transaction isolation level is set to read uncommitted.

 

==From Grant Fritchey’s Book==

 

These are the characteristics of dynamic cursors:

·       Dynamic cursors operate directly on the base table(s).

·       The membership of rows in the cursor is not fixed, since they operate directly on the base table(s).

·       As with forward-only cursors, rows from the underlying table(s) are not retrieved until the cursor rows are fetched using a cursor FETCH operation.

·       Dynamic cursors support all scrolling options except FETCH ABSOLUTE n, since the membership of rows in the cursor is not fixed.

·       These cursors allow all changes through the cursor. Also, all changes made to the underlying table(s) are reflected in the cursor.

·       Dynamic cursors don’t support all properties and methods implemented by the database API cursors. Properties such as AbsolutePosition, Bookmark, and RecordCount, as well as methods such as clone and Resync, are not supported by dynamic cursors. Instead, they are supported by keyset-driven cursors.

·       The dynamic cursor is absolutely the slowest possible cursor to use in all situations. It takes more locks and holds them longer, which radically increases its poor performance. Take this into account when designing your system.

 

Dynamic Cursor – Example

-- Dynamic Cursor for Update

 

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Dynamic_cur_empupdate CURSOR DYNAMIC

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Dynamic_cur_empupdate

 

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Dynamic_cur_empupdate INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Mohan'

                    UPDATE  Employee

                    SET     Salary = 15000

                    WHERE CURRENT OF Dynamic_cur_empupdate

                FETCH NEXT FROM Dynamic_cur_empupdate INTO @Id, @name

            END

    END

 

CLOSE Dynamic_cur_empupdate

 

DEALLOCATE Dynamic_cur_empupdate

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

-- Dynamic Cursor for DELETE

 

SET NOCOUNT ON

 

DECLARE @Id INT

DECLARE @name VARCHAR(50)

 

DECLARE Dynamic_cur_empdelete CURSOR DYNAMIC

FOR

    SELECT  EmpID ,

            EmpName

    FROM    Employee

    ORDER BY EmpName

 

OPEN Dynamic_cur_empdelete

IF @@CURSOR_ROWS > 0

    BEGIN

        FETCH NEXT FROM Dynamic_cur_empdelete INTO @Id, @name

        WHILE @@Fetch_status = 0

            BEGIN

                IF @name = 'Deepak'

                    DELETE  Employee

                    WHERE CURRENT OF Dynamic_cur_empdelete

                FETCH NEXT FROM Dynamic_cur_empdelete INTO @Id, @name

            END

    END

 

CLOSE Dynamic_cur_empdelete

 

DEALLOCATE Dynamic_cur_empdelete

 

SET NOCOUNT OFF

Go

 

SELECT  *

FROM    Employee

 

 

4)             READ_ONLY vs. SCROLL_LOCKS vs. OPTIMISTIC: specify concurrency of a cursor. Default to READ_ONLY. 

 

READ_ONLY – cannot update data through the cursor

 

Prevents updates made through this cursor. The cursor cannot be referenced in a WHERE CURRENT OF clause in an UPDATE or DELETE statement. This option overrides the default capability of a cursor to be updated.

 

SCROLL_LOCKS – can update/delete data with success as it locks the data when the cursor reads the data

 

Specifies that positioned updates or deletes made through the cursor are guaranteed to succeed. SQL Server locks the rows as they are read into the cursor to ensure their availability for later modifications. SCROLL_LOCKS cannot be specified if FAST_FORWARD or STATIC is also specified.

 

OPTIMISTIC – do not lock the data. If the row has NOT been updated since it was read into the cursor, then the update/delete can be done through the cursor. If the row has been updated (through other ways) since it was read into the cursor, then the update/delete through the cursor will fail.

 

Specifies that positioned updates or deletes made through the cursor do not succeed if the row has been updated since it was read into the cursor. SQL Server does not lock rows as they are read into the cursor. It instead uses comparisons of timestamp column values, or a checksum value if the table has no timestamp column, to determine whether the row was modified after it was read into the cursor. If the row was modified, the attempted positioned update or delete fails. OPTIMISTIC cannot be specified if FAST_FORWARD is also specified. 

 

5)             TYPE_WARNING

Specifies that a warning message is sent to the client when the cursor is implicitly converted from the requested type to another.

 

DECLARE SOID CURSOR GLOBAL STATIC TYPE_WARNING

FOR

    SELECT  SalesOrderID

    FROM    Sales.SalesOrderHeader

WHERE   OnlineOrderFlag = 1;

 

6)             SELECT_STATEMENT

Is a standard SELECT statement that defines the result set of the cursor. The keywords COMPUTE, COMPUTE BY, FOR BROWSE, and INTO are not allowed within select_statement of a cursor declaration.

 

Note: You can use a query hint within a cursor declaration; however, if you also use the FOR UPDATE OF clause, specify OPTION (query_hint) after FOR UPDATE OF.


SQL Server implicitly converts the cursor to another type if clauses in select_statement conflict with the functionality of the requested cursor type. For more information, see Implicit Cursor Conversions.

 

7)             FOR UPDATE [OF column_name [,...n]]

Defines updatable columns within the cursor. If OF column_name [,...n] is supplied, only the columns listed allow modifications. If UPDATE is specified without a column list, all columns can be updated, unless the READ_ONLY concurrency option was specified.

 

The following example is from How to Use Update Cursors in SQL Server on http://www.codeproject.com/Articles/232452/How-to-Use-Update-Cursors-in-SQL-Server

 

There can be a situation where you have to use a cursor, even though the experts say not to use cursors or to avoid them as much as possible. But if you look closely, most of the time we use cursors to iterate through a row collection and update the same table.

 

In these type of situations, it is ideal to use an Update Cursor, than using the default read only one.

 

Consider the following table:

 

CREATE TABLE [dbo].[SAMPLE_EMPLOYEE](

    [EMP_ID] [intNOT NULL,

[RANDOM_GEN_NO] [VARCHAR](50NULL

) ON [PRIMARY]

 

Insert few records to the above table using the following script:

 

SET NOCOUNT ON

DECLARE @REC_ID AS INT

SET @REC_ID = 1

WHILE ( @REC_ID <= 1000 )

    BEGIN

        INSERT  INTO SAMPLE_EMPLOYEE

                SELECT  @REC_ID ,

                        NULL

   

        IF ( @REC_ID <= 1000 )

            BEGIN

                SET @REC_ID = @REC_ID + 1

                CONTINUE

            END 

        ELSE

            BEGIN

                BREAK

            END

    END

SET NOCOUNT OFF

 

Next, we will add a Primary Key using the below script (or you can use the table designer):

 

ALTER TABLE [dbo].[SAMPLE_EMPLOYEE]

ADD CONSTRAINT [PK_SAMPLE_EMPLOYEE] PRIMARY KEY CLUSTERED

(

[EMP_ID] ASC

)WITH (PAD_INDEX  = OFFSTATISTICS_NORECOMPUTE  = OFF,

SORT_IN_TEMPDB = OFFIGNORE_DUP_KEY = OFFONLINE = OFF,

ALLOW_ROW_LOCKS  = ONALLOW_PAGE_LOCKS  = ONON [PRIMARY]

 

** Please note: A primary key should be there if we are to use an update cursor. Otherwise the cursor will be read only.

 

Here is how you use the Update Cursor. I have highlighted the areas which differ compared with a normal cursor. You have to mention which column you are going to update (or all columns in your selection will be updatable) and you have to use ‘where current of <cursor>’ in your update statement.

 

SET NOCOUNT ON

DECLARE @EMP_ID AS INT ,

    @RANDOM_GEN_NO AS VARCHAR(50) ,

    @TEMP AS VARCHAR(50)

 

DECLARE EMP_CURSOR CURSOR

FOR

    SELECT  EMP_ID ,

            RANDOM_GEN_NO

    FROM    SAMPLE_EMPLOYEE FOR UPDATE OF RANDOM_GEN_NO

 

OPEN EMP_CURSOR

 

FETCH NEXT FROM EMP_CURSOR

INTO @EMP_ID, @RANDOM_GEN_NO

 

WHILE @@FETCH_STATUS = 0 )

    BEGIN

        SELECT  @TEMP = FLOOR(RAND() * 10000000000000)

        UPDATE  SAMPLE_EMPLOYEE

        SET     RANDOM_GEN_NO = @TEMP

        WHERE CURRENT OF EMP_CURSOR

   

        FETCH NEXT FROM EMP_CURSOR

    INTO @EMP_ID, @RANDOM_GEN_NO

    END

 

CLOSE EMP_CURSOR

DEALLOCATE EMP_CURSOR