SQL  

Cursors vs Sets in SQL

Introduction

They say not to use Cursors. They are right and wrong at the same time.

If Cursors are bad, why is it not removed from SQL?

Background

Cursors are probably slow in performance concerning normal code (Sets). Therefore, we avoid using them. But at the same time, it is unavoidable and is proffered to be used in cases where there is a need to prepare dynamic SQL or complex logics row by row.

The article primarily focuses on determining a boundary between the two, Cursors and Sets.

Explanation

Cursor in SQL

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

The cursor iterates through every single row for processing, and each time it fetches a row, it makes a network round trip. Since it makes the round trips, the network bandwidth would go for a toss, and repeatedly doing this can directly impact the operation used with the Cursor.

The following is a simple description of 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 from the cursor, one row at a time. 
  4. Close the cursor when done.

Here is the sample code of a cursor:

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

Sets in SQL

The fundamental approach of SQL is to differentiate a pool of data logically. SQL works on sets, in other words, with a set of records. Therefore, Sets can replace the cursor to a maximum level. These are normal SQL queries. The following example shows the difference between them.

Example

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

Solution using the Cursor

Here is what the code says.

  1. Fetch the records (Telephone numbers) with pincode null from the table Customer. 
  2. Iterate to every fetched record and break the preceding 4 digits of the 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 null and update the pincode into the Customer table.
    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
    SQL

Solution using the Sets

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

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
SQL

Advantage of Sets over Cursor in the above example

  1. Sets are recommended since there will be a noticeable improvement in the query results. 
  2. The code is easily readable and understandable. 
  3. There will be no network round trips. 
  4. The query can be optimized with indexing.

When can we use cursors?

Sets are only for use where the developer builds the query with prior knowledge of what the user will see or use.

  1. Cursors can be used when the user is given an interface to group the data logically. Then, the developer would have no idea of what kind of grouping will be done by the user. 
  2. Or, as in the following example, if an event must be fired to update a table present in all the databases ('ClientProductionOne,' 'ClientProductionTwo,' 'ClientProductionThree'), then a cursor approach will help you.
    DECLARE @dbname VARCHAR(50)
    DECLARE @databasename VARCHAR(256)
    DECLARE @SQL 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 @SQL    = REPLACE(@SQL, '@dbname', @dbname)
        PRINT   @SQL
    --      EXEC    (@SQL)
            FETCH NEXT FROM db_cursor INTO  @dbname
    END
    CLOSE db_cursor
    DEALLOCATE db_cursor
    GO
    SQL

Summary

In this article, we have tried to help the developers determine the approach (on choosing Cursor or Sets) which is probably better. And to 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.