Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / database / data-visualization

Cursors Vs. Sets

4.78/5 (7 votes)
19 Nov 2014CPOL3 min read 19.1K  
A brief discussion of when to use cursors and when to use sets.

Introduction

Cursors, they say, do not use them. They are absolutely right... and wrong at the same time. :-)

If Cursors are that bad, then why are they not removed from SQL??

Background

Cursors are probably slow in performance with respect to normal code (Sets), therefore, we avoid using them. But at the same time, they are unavoidable, and are preferred to be used in cases where there is a need to prepare dynamic SQL or complex logics on a row by row basis.

This tip is primarily to focus on determining a boundary between the two (Cursors and Sets).

Explained

Cursor

If developers have worked in VB (Visual Basic) precisely in recordsets, it works in a similar fashion to that of a cursor.

The cursor iterates through every single row for processing, each time when it fetches a row, it performs a network round trip. Since it is round tripped, the network bandwidth would go for a toss and repeatedly doing this can have a direct impact of the operation used in cursor.

Following is a simple code on how cursors are used in SQL procedures:

  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.

Here is the sample code of a cursor:

SQL
DECLARE cust_cursor CURSOR
    FOR SELECT * FROM Cutomers
OPEN cust_cursor
FETCH NEXT FROM cust_cursor;
CLOSE cust_cursor 

Sets

SQL works on sets, i.e., with set of records. The fundamental approach of SQL is to differentiate a pool of data logically. Therefore, Sets can replace the cursor to maximum level.These are normal SQL queries.

The below example shows the difference between them.

Example

Problem: Update all the records in Customer table with respective pincode using table Pincode_Details.

Solution using the Cursor

Here is what the code says:

  1. Fetch the records (Telephone numbers) having pincode null, from table Customer.
  2. Iterate every fetched record, and break the preceding 4 digits of telephone number.
  3. Find the respective pincode from Pincode_details using the number fetched in step 2.
  4. For every record, check if the variable @pincode is not a null and update the pincode into Customer table.
SQL
DECLARE @telnumber char(8)
DECLARE cust_cursor CURSOR FOR					--
   SELECT TelNumber FROM Customer WHERE PinCode IS NULL		--
OPEN cust_cursor						--
FETCH NEXT FROM cust_cursor  INTO @telnumber			-- (1)
WHILE @@FETCH_STATUS = 0 BEGIN
   Declare @pincode char(6)
   DECLARE @centerid char(4)
   SELECT @centerid = LEFT(@telnumber, 4)			-- (2)

   SELECT @pincode = PinCode 					--
   FROM PinCode_Details 					--
   WHERE Centerid = @centerid					-- (3)

   IF @pincode IS NOT NULL 					--
   BEGIN							--
       UPDATE Customer SET PinCode = @pinCode			--
       WHERE CURRENT OF cust_cursor 				--
   END								-- (4)
   FETCH NEXT FROM cust_cursor INTO @telnumber
END
CLOSE cust_cursor 
DEALLOCATE cust_cursor

Solution using the Sets

A single update query with join will achieve the same result.

SQL
UPDATE Customer 
SET PinCode = PinCode_Details.PinCode 
FROM Customer
JOIN PinCode_Details ON
    LEFT(Customer.PhoneNumber, 4) = PinCode_Details.Centerid
WHERE
    Customer.PinCode IS NULL

Advantage of Sets over Cursor in the Above Example

  1. 'Sets' is recommended as there will be a noticeable improvement in the query results.
  2. Code is easily readable and understandable.
  3. There will be no network round trips.
  4. Query can be optimized with indexing.

Now the Question is, When Can We Use the Cursor?

Sets are only bound to be used where the developer builds the query with a prior knowledge of what user is going to see or use.

  1. Cursors can be used in a situation where the user is given an interface to logically group the data. Then, the developer would have no idea on what kind of grouping will be done by the user.
  2. Or as per the below example, if an event has to be fired to update a table present in all the databases ('ClientProductionOne','ClientProductionTwo','ClientProductionThree'), then approach of cursor will help you.
SQL
DECLARE @dbname VARCHAR(50)
DECLARE @databasename VARCHAR(256)
DECLARE @SQL varchar(8000)
DECLARE @ExecSQL varchar (8000)
 
SET    @SQL = 'UPDATE  @dbname.dbo.tbldailyEventFired
        SET EndTime = CONVERT(datetime,''2014-11-18 23:59:00'',120) 
        WHERE EndTime = (CONVERT(datetime,''2015-11-17 23:59:00'',120))'
 
DECLARE db_cursor CURSOR FOR 
    SELECT name 
    FROM master.dbo.sysdatabases
    WHERE name IN ('ClientProductionOne','ClientProductionTwo','ClientProductionThree')
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO @dbname
WHILE    (@@FETCH_STATUS = 0)
BEGIN
    SET    @ExecSQL    = REPLACE(@SQL, '@dbname', @dbname)
    EXEC    (@ExecSQL)
        FETCH NEXT FROM db_cursor INTO  @dbname 
END
CLOSE db_cursor
DEALLOCATE db_cursor
GO

Points of Interest

In this tip, we have tried to help developers determine the approach (on choosing Cursor or Sets), probably a better one and to clearly point out the right choice of using the same.

Thanks to all my distinguished seniors / colleagues of my career, for passing on their views and approaches when using Cursors and Sets.

History

  • 18th November, 2014: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)