#0178-SQL Server-CLOSE and DEALLOCATE cursor-A lesson for sustenance teams-Peer Reviews help resolve problems


I have worked for a product sustenance team for the better part of my 7+ years of experience and hence, I can closely relate to the problem at hand today. The challenges with sustenance teams are many, and one of them is understanding and building upon code written by other teams – developers of whom may not even be around in the organization. In addition to this the code that needs to be extended may be legacy code which was written years ago and may not confirm to the newer coding standards. To reduce the impact, most sustenance teams therefore do not spend the time to re-engineer or re-write a piece of code just because it is not up to standards, but instead work on extending the functionality. They leave it to the new development teams to re-write the code as part of the system refresh cycles.

The Scenario

This took the better of one of the engineers recently. An existing procedure (written in the old-fashioned way to use CURSORs) had to be extended. It was upto this poor developer to extend the stored procedure to incorporate some additional logic and release it out to QA within a considerably tight time-frame.

This stored procedure used the slower approach of using CURSORs to iterate through the list of employees. This cursor was to be re-used after another set of business operators to achieve the new requirement. The engineer therefore decided to add the additional piece of code after the old code ended – which is where he made a fatal mistake that cost him a couple of hours to notice.

The Example

To establish a parallel with the requirement, let us assume that a procedure exists to increment the VacationHours of all employees of a certain grade with AdventureWorks Cycles by 1 hour. We will extend this stored procedure by attempting to revert back the change to VacationHours, leaving a small error in doing so.

USE AdventureWorks2012
GO
IF OBJECT_ID('HumanResources.proc_DemoDellocateCursorIssues') IS NOT NULL
BEGIN
    DROP PROCEDURE HumanResources.proc_DemoDellocateCursorIssues
END
GO
CREATE PROCEDURE HumanResources.proc_DemoDellocateCursorIssues
    @orgLevel INT
AS
BEGIN
    SET NOCOUNT ON
    DECLARE @businessEntityId INT 

    DECLARE employeeCursor INSENSITIVE CURSOR
    FOR (SELECT e.BusinessEntityID
         FROM HumanResources.Employee AS e
         WHERE e.OrganizationLevel = @orgLevel
        )

    OPEN employeeCursor 

    BEGIN TRANSACTION outerTran
        BEGIN TRY
            FETCH NEXT FROM employeeTran INTO @businessEntityId

            WHILE (@@FETCH_STATUS <> -1)
            BEGIN
                WHILE (@@FETCH_STATUS <> -2)
                BEGIN
                    UPDATE e
                    SET VacationHours += 1
                    FROM HumanResources.Employee AS e
                    WHERE BusinessEntityID = @businessEntityId
                END
                FETCH NEXT FROM employeeTran INTO @businessEntityId
            END

            --IF @@TRANCOUNT > 0
            --    COMMIT TRANSACTION outerTran
        END TRY
        BEGIN CATCH
            IF @@TRANCOUNT > 0
                ROLLBACK TRANSACTION outerTran
        END CATCH

        CLOSE employeeCursor
        DEALLOCATE employeeCursor

        --OLD CODE ENDS HERE!!! ADDITIONAL CODE BEGINS

        --This part did not exist before
        --Some more business logic added here

        --Re-open the cursor and begin processing
        OPEN employeeCursor
        BEGIN TRY
            FETCH NEXT FROM employeeTran INTO @businessEntityId

            WHILE (@@FETCH_STATUS <> -1)
            BEGIN
                WHILE (@@FETCH_STATUS <> -2)
                BEGIN
                    UPDATE e
                    SET VacationHours -= 1
                    FROM HumanResources.Employee AS e
                    WHERE BusinessEntityID = @businessEntityId
                END
                FETCH NEXT FROM employeeTran INTO @businessEntityId
            END
            IF @@TRANCOUNT > 0
                COMMIT TRANSACTION outerTran
        END TRY
        BEGIN CATCH
            IF @@TRANCOUNT > 0
                ROLLBACK TRANSACTION outerTran
        END CATCH

        CLOSE employeeCursor
        DEALLOCATE employeeCursor
END
GO

The Error

All those who have noticed the error, no further reading of this post is necessary. All those who have not found the error yet, please read on…

Executing the procedure raises the following error:

USE AdventureWorks2012
GO
EXEC HumanResources.proc_DemoDellocateCursorIssues @orgLevel = 4
GO

Msg 16916, Level 16, State 1, Procedure proc_DemoDellocateCursorIssues, Line 40
A cursor with the name ’employeeCursor’ does not exist.
Msg 16916, Level 16, State 1, Procedure proc_DemoDellocateCursorIssues, Line 67
A cursor with the name ’employeeCursor’ does not exist.
Msg 16916, Level 16, State 1, Procedure proc_DemoDellocateCursorIssues, Line 68
A cursor with the name ’employeeCursor’ does not exist.

The error comes simply because the CURSOR – employeeCursor has been DEALLOCATEd before being used again. Let’s focus on the part where the old code ends and new extension was added:

        CLOSE employeeCursor
        --Notice the DEALLOCATE from the older implementation here. The solution is to comment this DEALLOCATE statement
        --DEALLOCATE employeeCursor

        --OLD CODE ENDS HERE!!! ADDITIONAL CODE BEGINS

        --This part did not exist before
        --Some more business logic added here

        --Re-open the cursor and begin processing
        OPEN employeeCursor

It was a simple typographical error on the part of the developer, but resulted in his spending an hour or two in debugging the issue and ultimately approaching me for help. A 10-minute study of the code from a different pair of eyes (mine) helped him resolve realize the miss and resolve the issue.

Moral of the story

The moral of the story is not whether we should use Cursors or not, but there is a more important behavioural pattern that I would like to highlight.

After being sure that we have done our level best in writing a piece of code, most of us tend to keep troubleshooting the code for hours at an end if it happens to develop an error. All I urge is that if you are sure that you have done due diligence from your end, do not be afraid to ask for peer reviews – they help because they involve a different pair of eyes on the same problem. The difficult part is not making it a habit by having others resolve all your problems for you.

Until we meet next time,

Be courteous. Drive responsibly.

Advertisements

One thought on “#0178-SQL Server-CLOSE and DEALLOCATE cursor-A lesson for sustenance teams-Peer Reviews help resolve problems

  1. Pingback: #0386 – SQL Server – Cursor Scope – A cursor with the name ‘cursor name’ does not exist.; Msg 16916 | SQLTwins by Nakul Vachhrajani

Let me know what you think about this post by leaving your feedback here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s