What is a cursor and why is it used in SQL Server?

Cursors in SQL Server

A SQL cursor is a database object which is used to manipulate data in a set, row-by-row processing, instead of the T-SQL commands that operate on ALL the rows in the result set at one time.

Key Takeaways

  1. SQL Cursor Overview:
    • Cursors in SQL Server allow row-by-row data manipulation, contrasting with T-SQL’s full result set operations. While useful for specific tasks, they can very negatively impact performance.
  2. Cursor Types and Challenges:
    • SQL Server offers various cursor types: STATIC, FAST_FORWARD, DYNAMIC, and KEYSET. Their misuse, primarily due to a misunderstanding of set operations, can lead to performance issues and table locking.
  3. Cursors vs. Alternatives:
    • Temporary tables are a notable alternative to cursors. They store result sets and, with set-based operations, can offer better performance. It’s essential to favor set operations over row-by-row approaches in SQL for optimized results.

When to use Cursors?

There are some conditions when we want to get a record from one table and need to insert it into another with performing some condition or logic. A cursor is a similar approach to any looping mechanism found in any other programming.

What are the biggest problems with Cursors?

Problems when it comes to performance.

When Cursor is OK to use?

They are great for one time type of work. Example: pull a multiple rows, do multiple actions on them. Maybe even execute PowerShell or xp_cmdshell. As a DBA, I use this quite a bit. Take one row, do bunch of actions, move to another row. This is when I don’t care what performance looks like. I just need to execute multiple commands per each row.

When NOT to use Cursors?

Don’t put Cursor into anything that will be used very frequently. Especially, Stored Proc that will need to run fast. Or very frequently. That will not be as fast as using other methods.

By the way, this check is a part of our SQL Server Health Check service.

How to Create a SQL Server Cursor?

Life Cycle of SQL Server cursor
Figure 1 – The Life Cycle of SQL Server cursor.

The process of using a SQL Cursor (starting with MS SQL 2008) is:

  1. Declaring Cursor by defining the SQL statement.
  2. Opening the Cursor for storing data retrieved from the result set.
  3. Rows can be fetched from the cursor one by one or in a block to do data manipulation.
  4. The cursor should be closed explicitly after data manipulation.
  5. Cursors should be deallocated to delete cursor definition and release all the system resources.

Cursor Syntax

Figure 2 – MS SQL Server Cursor Syntax.

What are the different Cursor types?

Microsoft SQL Server supports the following types of cursors.

  1. STATIC CURSOR
    Populates the result set during cursor creation and the query result is cached for the lifetime of the cursor. A static cursor can move forward and backward.
  2. FAST_FORWARD (default type of cursor)
    It is identical to the static except that you can only scroll forward.
  3. DYNAMIC
    Additions and deletions are visible for others in the data source while the cursor is open. Unlike static cursors, all the changes made in the Dynamic cursor will reflect the Original data. You can use this cursor to perform INSERT, DELETE, and UPDATE operations.
  4. KEYSET
    This is like a dynamic cursor except we can’t see records others add. If another user deletes a record, it is inaccessible from our record set.

Cursor Examples

The example below renames all tables in the sample database named as Spreadsheet, by adding ‘_Backup’ to each table’s name while also ensuring that tables containing ‘_Backup’ in their name will not be renamed again by running the following code:

DECLARE @TableName VARCHAR(50) — Old table name
,@NewTableName VARCHAR(50) — New table name
SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES T
where TABLE_CATALOG = ‘SpreadSheet’ — Get all tables name from database SpreadSheet
OPEN Student_Cursor
FETCH NEXT FROM Student_Cursor INTO @TableName
WHILE @@FETCH_STATUS = 0
BEGIN
IF RIGHT(@TableName,6)<>’Backup’ — If Backup table does not exist, then rename the table
BEGIN
SET @NewTableName=@TableName+’_Backup’ — Add ‘_Backup’ to current name
EXEC sp_rename @TableName,@NewTableName — Rename the table
END
ELSE
PRINT ‘Backup table name already exists: ‘+@TABLENAME

FETCH NEXT FROM Student_Cursor — Get next row data into cursor and store it in variables
INTO @TableName
END
CLOSE Student_Cursor — Close cursor locks on the rows
DEALLOCATE Student_Cursor — Release cursor reference

Figure 3 – SQL Server Cursor sample.

Template of How to Declare a Cursor in SQL Server

DECLARE @product_id INT;
DECLARE @product_name VARCHAR(50);

DECLARE product_cursor 
CURSOR FOR 
   SELECT product_id, product_name FROM products;
OPEN product_cursor;
FETCH NEXT FROM product_cursor INTO @product_id, @product_name;
WHILE @@FETCH_STATUS = 0
BEGIN
    -- Do something with the row data
    PRINT 'Product ID: ' + CAST(@product_id AS VARCHAR(10));
    PRINT 'Product Name: ' + @product_name;
    -- Fetch the next row
    FETCH NEXT FROM product_cursor INTO @product_id, @product_name;
END;
CLOSE product_cursor;
DEALLOCATE product_cursor;

In this example, the cursor product_cursor is declared to fetch the product_id and product_name columns from the products table. The OPEN statement opens the cursor, and the FETCH NEXT statement retrieves the first row from the result set and stores the values in the variables @product_id and @product_name.

The WHILE loop iterates through the rows in the result set, processing each row in turn. The @@FETCH_STATUS function returns the status of the last fetch operation, so the loop continues as long as there are more rows to fetch. The FETCH NEXT statement retrieves the next row from the result set and stores the values in the variables again.

Finally, the CLOSE statement closes the cursor, and the DEALLOCATE statement frees the memory used by the cursor. It is important to always close and deallocate cursors when you are done using them, to avoid memory leaks and other issues.

How to Fetch Data Using a Cursor in SQL Server

A cursor is a database object that allows users to traverse through the result set of a SELECT statement one row at a time. Cursors are often used when working with complex queries that return large amounts of data, as they provide a way to process the data row by row.

To fetch data using a cursor in SQL Server, you can follow these basic steps:

  1. Declare a cursor: To declare a cursor, you need to specify its name and the SELECT statement that defines the result set for the cursor. For example, you can declare a cursor named “myCursor” as follows:
    DECLARE myCursor CURSOR FOR
    SELECT column1, column2, column3
    FROM myTable
    WHERE column4 = 'value';
    
  2. Open the cursor: Once you have declared the cursor, you need to open it using the OPEN statement. This will initialize the cursor and make the first row of the result set available for processing. For example:
    OPEN myCursor;
    
  3. Fetch data from the cursor: To fetch data from the cursor, you can use the FETCH statement. This will retrieve the next row of the result set and make it available for processing. For example:
    FETCH NEXT FROM myCursor INTO @column1, @column2, @column3;
    

    Here, “@column1”, “@column2”, and “@column3” are variables that you have declared to store the values of the columns retrieved by the FETCH statement.

  4. Process the data: Once you have fetched the data from the cursor, you can process it as needed. This may involve performing calculations, updating values, or inserting data into another table.
  5. Repeat steps 3-4 until all rows have been processed: To process all rows in the result set, you need to repeat steps 3-4 until the FETCH statement returns a value of NULL, indicating that there are no more rows to fetch. For example:
    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- Process data here
    
        FETCH NEXT FROM myCursor INTO @column1, @column2, @column3;
    END
    

    Here, the WHILE loop will continue to execute as long as the FETCH statement returns a value of 0, indicating that there are still rows to be processed.

  6. Close the cursor: Once you have finished processing the data, you should close the cursor using the CLOSE statement. This will release any resources that were allocated for the cursor. For example:
    CLOSE myCursor;
    
  7. Deallocate the cursor: Finally, you should deallocate the cursor using the DEALLOCATE statement. This will remove the cursor from memory. For example:
    DEALLOCATE myCursor;
    

By following these steps, you can use a cursor to fetch data from a result set in SQL Server and process it row by row. However, it’s important to note that cursors can be resource-intensive and should be used judiciously. In some cases, it may be more efficient to use other techniques such as temporary tables or set-based operations.

Cursor Limitations

Cursors are used frequently as the wrong tool for the wrong task. They’re used for quick programming when a developer does not have a full understanding of set operations.

The problem with the cursor is that, in most cases, we can use JOINS, even WHILE clauses, SSIS packages, or other alternative methods to get the same result quicker, with less impact on performance output and even writing fewer lines of syntax.

The principal factor problem affecting cursor speed is the number of rows and columns brought into it. While a cursor is being populated, the table being iterated is locked. Row operations can take a long time to execute depending upon the type of task being performed on each row. While the cursor is open, a table cannot be accessed or updated by the other users.

Cursors could be used in some applications for serialized, but generally, they should be avoided because they bring a negative impact on performance, especially when operating on a large set of data.

SQL Server Cursor Alternatives

There are many alternatives for SQL cursors, so let’s look at the temporary table strategy in detail. Temporary tables have been in use for a long time and provide an excellent way to replace cursors for large data sets. Temporary tables can hold the result set so that we can perform the necessary operations by processing it with an iterating algorithm such as a ‘while’ loop.

We will use a temporary table to rename all tables in the sample by following these steps:

  1. Declare a temporary table;
  2. Store tables names and ids;
  3. Set the counter to 1 and get the total number of records from the temporary table;
  4. Use a ‘while’ loop as long as the counter is less than or equal to the total number of records;
  5. Within the loop, rename tables one by one if they are not already renamed and increase the counter for each table
DECLARE @TableName VARCHAR(50) — Old table name
       ,@NewTableName VARCHAR(50) — New table name
   ,@TotalRows INT –Total amount of rows
   ,@i INT– Loop counter
CREATE TABLE #TargetTables — Declaring a temporary table
(
  TableId INT,
TableName VARCHAR(40)
)
INSERT INTO #TargetTables — insert into the temporary table
SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES T
where TABLE_CATALOG = ‘SpreadSheet’ — Get all tables name from database SpreadSheet
SET @TotalRows= (SELECT COUNT(*) FROM #TargetTables)– Get the total amount of rows and
SET @i=1 — Set the counter to 1
WHILE @i<=@TotalRows — begin as long as i (counter) is less than or equal to the total number of records
BEGIN — Loop begins here
SELECT @TableName=TableName from #TargetTables WHERE TableId=@i
IF RIGHT(@TableName,6)<>’Backup’ — If a Backup table does not exist then rename the table
BEGIN
SET @NewTableName=@TableName+’_Backup’ — Add ‘_Backup’ to current name
EXEC sp_rename @TableName,@NewTableName — Rename the table
END
ELSE
PRINT ‘The table name already exists: ‘+@TABLENAME
SET @i=@i+1
END — While loop ends here
DROP TABLE #TargetTables
Figure 4 – Alternative code to a SQL Cursor.

Conclusions

It`s a good practice in TSQL to think at making operations on sets of data, rather than thinking programmatically, and processing row by row in the loop. Cursors are a great tool and not always evil, but when performance matters, just find another way to accomplish the same thing.

Let our expert SQL Server Consulting team handle the technical complexities, so you can focus on driving your business forward with confidence.

Frequently Asked Questions

  1. Is it possible to use Cursors within stored procedures or functions in SQL Server?
    • Yes, cursors can be implemented within stored procedures and functions. However, it’s essential to be cautious as using cursors within these objects can magnify performance implications, especially when the stored procedure or function is called frequently. It’s generally recommended to look for set-based alternatives first before resorting to cursors in such scenarios.
  2. Why might one use a Cursor instead of traditional T-SQL commands?
    • Cursors are beneficial when there’s a need for sequential processing of records, especially when each row’s processing depends on the previous row or when certain conditions and logic need to be applied to individual rows. This is different from set-based operations where actions are applied to the whole dataset simultaneously.
  3. Can you nest Cursors within SQL Server?
    • Yes, you can nest cursors by defining a cursor within another cursor’s operation. However, nesting can add layers of complexity and further magnify performance issues. If nesting is deemed necessary, it’s crucial to maintain clarity in the code and ensure each cursor is appropriately managed through its lifecycle to prevent resource leaks.
  4. What are the common pitfalls or disadvantages of using Cursors?
    • Cursors can lead to performance issues, especially with large datasets. They often lock tables during iteration, preventing other users from accessing or updating the table. Additionally, cursors might be used inappropriately due to a lack of understanding of set operations, which can result in inefficient code.
  5. Are there alternatives to using Cursors in SQL Server?
    • Yes, several alternatives can often provide more efficient solutions. Examples include using JOIN operations, WHILE loops, SSIS packages, and temporary tables. These methods can often achieve the same goals as a cursor but with better performance and fewer system resource requirements.
  6. How do Cursors impact transaction locking and concurrency in SQL Server?
    • Cursors can have significant impacts on transaction locking. When a cursor is processing a row or a set of rows, those rows might be locked, preventing other operations from accessing or modifying them. This can lead to concurrency issues, especially in high-transaction environments. Developers should be mindful of the isolation levels set for cursors and consider the implications for overall database performance and user experience.
  7. How do forward-only and scrollable cursors differ in SQL Server?
    • Forward-only cursors allow you to retrieve rows in the result set sequentially, moving only from the first row to the last. You cannot revisit previous rows. On the other hand, scrollable cursors provide more flexibility, allowing you to move both forward and backward through the result set, as well as jump to specific rows. This added flexibility can come at the cost of performance, as scrollable cursors usually consume more resources.
Picture of Mark Varnas

Mark Varnas

Hey I'm Mark, one of the guys behind Red9. I make a living performance tuning SQL Servers and making them more stable. I channel my SQL into our SQL Managed Services, SQL Consulting and our internal database products.

Leave a Reply

Your email address will not be published. Required fields are marked *