T-SQL Cursor vs Merge

Today we’ll have a look at Microsoft SQL Server programming and specifically the CURSOR and MERGE statements.

A cursor can be used to loop through a set of rows programmatic-ally.  Their use is discouraged because they tend to perform poorly.  It’s usually faster to work with a set of records instead of ‘walking through’ a table, one row at a time.

Let’s have a look at the difference in performance between a cursor and the merge statement. 

Imagine a table with stock prices (dbo.Securities) that is updated with new prices every day.
After each update, we want to move SecurityID, Price and PriceDate to the dbo.PriceHistory table. 

We will need to check if the combination of SecurityID and PriceDate exists in the target table.  If it doesn’t then we will INSERT a new row.  If it does exist then we will have to UPDATE the existing record. 

We could write a stored procedure that uses a cursor as follows:

CREATE PROCEDURE dbo.SP_InsertPriceHistory AS
SET NOCOUNT ON;
DECLARE @MySecID int;
DECLARE @MyDate date;
DECLARE @MyPrice numeric(12,4);

DECLARE MyCur CURSOR FAST_FORWARD
  FOR SELECT Securityid, Price, PriceDate
  FROM dbo.Securities ORDER BY SecurityID;
OPEN MyCur;
FETCH NEXT FROM MyCur INTO @MySecID, @MyPrice, @MyDate;
WHILE @@FETCH_STATUS = 0
BEGIN
  –Check if record exists
    

  IF EXISTS (SELECT * FROM dbo.PriceHistory

             WHERE PriceDate = @MyDate and SecurityID = @MySecID)
    BEGIN
      –Record exists – Update
      UPDATE dbo.PriceHistory SET PriceDate = @MyPrice
        WHERE (SecurityID = @MySecID AND PriceDate = @MyDate);
    END
  ELSE
    BEGIN
       
      –Record does not exist – Insert
      INSERT INTO dbo.PriceHistory (SecurityID, PriceDate, Price)
        VALUES(@MySecID, @MyDate, @MyPrice);
        
    END
  FETCH NEXT FROM MyCur INTO @MySecID, @MyPrice, @MyDate;
END
CLOSE MyCur;
DEALLOCATE MyCur;


We can achieve the same result using the Merge statement.  The stored procedure might look like this:

CREATE PROCEDURE dbo.SP_InsertPriceHistory AS
SET NOCOUNT ON;

MERGE INTO dbo.PriceHistory AS TGT
  USING dbo.Securities AS SRC
  ON SRC.SecurityID = TGT.SecurityID AND

    SRC.PriceDate = TGT.PriceDate
  WHEN MATCHED THEN

    UPDATE SET TGT.Price = SRC.Price;
  WHEN NOT MATCHED 
THEN
    INSERT VALUES(SRC.SecurityID, SRC.PriceDate, SRC.Price);


SQL server will check for a match between the source and target table by comparing both the SecurityID and PriceDate columns.

If there is a match then it will run the code after WHEN MATCHED THEN

If there is no match then it means that no price is stored for this particular security on this date and it will run the code after WHEN NOT MATCHED THEN

Not only is this code easier to read, it is also much faster.  
 
For more details on Merge have a look at Merge on Microsoft technet. 

PS we can slightly improve the performance of both stored procedures by checking if an update is really necessary.  We can do this by comparing the price of the source with the price of the target table.  If the price is the same then we can skip the update.

Using Merge in SQL Server 2008 R2

MERGE INTO [TARGETTABLE] AS Target
    USING [SOURCETABLE] AS SOURCE
    ON

        –unique key filtering condition
        Target.COLUMN1=SOURCE.COLUMN1 AND
        Target.COLUMN2=SOURCE.COLUMN2 AND
        Target.COLUMN3 =SOURCE.COLUMN3 AND
        Target.COLUMN4 =SOURCE.COLUMN4

     WHEN MATCHED THEN
        –update or delete query
        UPDATE SET
            Target.ColumnA=SOURCE.
ColumnA,
            Target.
ColumnB=SOURCE.ColumnB,
            Target.
ColumnC=SOURCE.ColumnC

    WHEN NOT MATCHED THEN
        –INSERT QUERY
        INSERT (COLUMNA,
ColumnB, ColumnC, ColumnD)
        VALUES(
                        SOURCE.
ColumnA,
                        SOURCE.
ColumnB,
                        SOURCE.
ColumnC,
                        SOURCE.
ColumnD                    )
    ;

The SQL Server failed to initialize VIA support library [QLVipl.dll].

Got an error today trying to start newly installed sql express service – The SQL Server failed to initialize VIA support library [QLVipl.dll]. This normally indicates the VIA support library does not exist or is corrupted. Please repair or disable the VIA network protocol. Error: 0x7e.

Solution to the problem:

  1. Open SQL server configuration manager
  2. Under  SQL server network configuration node select your instance
  3. Disable VIA protocol

USING CURSOR IN MSSQL 2008 R2

Using Cursor is one of my favorite option while i need to perform a loop for an undefined sets of data, though this might cost you operational in-effeciency and memory leakage problems. Hence i suggest you DO NOT use cursor until you need to perform row by row operations on a list of data.
Before Starting with an example, lets go through basic working principle of using cursor in MSSQL.
First you need to define a temporary variable (which is a normal variable) to fetch particular column value into it for operation.
Then you define the cursor name along with data-type you need to hold and the query that fetches a list of the values.
And after you get all these values you fetch data until all the data have been fetched.
Lets see an Example.

— temporary variable that holds the data from cursor 
DECLARE @ID int


— now define cursor and its source
DECLARE cursor_Author_ID
                  CURSOR READ_ONLY FOR 
                 SELECT au_id FROM authors


open cursor
OPEN cursor_Author_ID
 
–fetch first Data
 FETCH NEXT FROM cursor_Author_ID INTO 
               @ID
–keep fetching data until all data have been read
 WHILE @@FETCH_STATUS = 0 
              BEGIN
               —write your code here, use the ID

                                                                
                   –keep fetching data until all data have been read  

                    FETCH NEXT FROM cursor_Author_ID  INTO  
                    @ID  
           END 
you need to close the cursor like any file, after opening it
 CLOSE cursor_Author_ID  
— deallocate the cursor to release all the resources used by it and 
— also reuse the for other variables
 DEALLOCATE  cursor_Author_ID