cursor

40
cursor What is a cursor? Cursor is a variable in SQL Server Database which is used for row-by row operations. The cursor is so named because it indicates the current position in the resultset. Let us look this example. Here I am just taking 2 columns from a table and passing through the cursor and printing them. CREATE PROCEDURE Usp_cursor_test AS BEGIN –Declaring the variables needed for cursor to store data DECLARE @Name VARCHAR(50) DECLARE @EmptypeID INT –Declaring the Cursor cur_print For name and Emptypeid in the Employeedetails table DECLARE cur_print CURSOR FOR SELECT name, emptypeid FROM employee.employeedetails –After declaring we have to open the cursor OPEN cur_print

Upload: mano-haran

Post on 30-Oct-2014

63 views

Category:

Documents


4 download

DESCRIPTION

cursor

TRANSCRIPT

Page 1: cursor

cursor

What is a cursor?

Cursor is a variable in SQL Server Database which is used for row-by row operations. The cursor is so named because it indicates the current position in the resultset.

Let us look this example.

Here I am just taking 2 columns  from a table and passing through the cursor and printing them.

CREATE PROCEDURE Usp_cursor_test

AS

BEGIN

–Declaring the variables needed for cursor to store data

DECLARE @Name VARCHAR(50)

DECLARE @EmptypeID INT

–Declaring the Cursor cur_print For name and Emptypeid in the Employeedetails table

DECLARE cur_print CURSOR FOR

SELECT name,

emptypeid

FROM employee.employeedetails

–After declaring we have to open the cursor

OPEN cur_print

–retreives the First row from cursor and storing it into the variables.

FETCH NEXT FROM cur_print INTO @Name, @EmptypeID

– @@FETCH_STATUS returns the status of the last cursor FETCH statement issued against

– any cursor currently opened by the connection.

– @@FETCH_STATUS = 0 means The FETCH statement was successful.

Page 2: cursor

– @FETCH_STATUS = -1 The FETCH statement failed or the row was beyond the result set.

– @@FETCH_STATUS = -2 The row fetched is missing.

WHILE @@FETCH_STATUS = 0

BEGIN

–Operations need to be done,Here just printing the variables

PRINT @Name

PRINT @EmptypeID

–retreives the NExt row from cursor and storing it into the variables.z

FETCH NEXT FROM cur_print INTO @Name, @EmptypeID

END

–Closing the cursor

CLOSE cur_print

– removes the cursor reference and relase cursor from memory

– very Important

DEALLOCATE cur_print

END

Note:

Once cursor is opened we have to close the cursor After the usage cursor should be deallocated from the memory. As a DBA , I will not recommend the usage of cursors in all scenarios because it affects

performance, since for each result it will have a  network round trip which will cause a major performance issue in large data sets. You can make use of case statement instead of cursors for some scenarios.

nested cursorDECLARE @EntityId Varchar(16)

Page 3: cursor

DECLARE @PerfId Varchar(16)

DECLARE @BaseId Varchar(16)

DECLARE @UpdateStatus Int

DECLARE outerCursor CURSOR FOR

SELECT EntityId, BaseId

FROM outerTable

--Returns 204,000 rows

OPEN outerCursor

FETCH NEXT FROM outerCursor INTO @EntityId, @BaseId

WHILE @@FETCH_STATUS = 0

BEGIN

DECLARE innerCursor CURSOR FOR

SELECT PRFMR_ID

FROM innerTable

WHERE ENTY_ID = @BaseId

OPEN innerCursor

FETCH NEXT FROM innerCursor INTO @PerfId

Page 4: cursor

SET @UpdateStatus = @@FETCH_STATUS

WHILE @UpdateStatus = 0

BEGIN

UPDATE 200MilRowTable

SET ENTY_ID = @EntityId

WHERE PRFMR_ID = @PerfId

FETCH NEXT FROM innerCursor INTO @PerfId

SET @UpdateStatus = @@FETCH_STATUS

END

CLOSE innerCursor

DEALLOCATE innerCursor --clean up inner cursor

FETCH NEXT FROM outerCursor INTO @EntityId, @BaseId

END

CLOSE outerCursor

DEALLOCATE outerCursor –cleanup outer cursor

Page 5: cursor

--backup all SQL Server databases

DECLARE @name VARCHAR(50) -- database name DECLARE @path VARCHAR(256) -- path for backup files DECLARE @fileName VARCHAR(256) -- filename for backup DECLARE @fileDate VARCHAR(20) -- used for file name

SET @path = 'C:\Backup\'

SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112)

DECLARE db_cursor CURSOR FOR SELECT nameFROM MASTER.dbo.sysdatabasesWHERE name NOT IN ('master','model','msdb','tempdb')

OPEN db_cursor FETCH NEXT FROM db_cursor INTO @name

WHILE @@FETCH_STATUS = 0 BEGIN SET @fileName = @path + @name + '_' + @fileDate + '.BAK' BACKUP DATABASE @name TO DISK = @fileName

FETCH NEXT FROM db_cursor INTO @name END

CLOSE db_cursor DEALLOCATE db_cursor

http://www.sqlteam.com/http://sqlusa.com/bestpractices2005/doublecursor/

SQL Server Basics of CursorsCursor is a database objects to retrieve data from a result set one row at a time, instead of the T-SQL commands that operate on all the rows in the result set at one time. We use cursor when we need to update records in a database table in singleton fashion means row by row.

Life Cycle of Cursor

1. Declare Cursor

A cursor is declared by defining the SQL statement that returns a result set.

2. Open

Page 6: cursor

A Cursor is opened and populated by executing the SQL statement defined by the cursor.

3. Fetch

When cursor is opened, rows can be fetched from the cursor one by one or in a block to do data manipulation.

4. Close

After data manipulation, we should close the cursor explicitly.

5. Deallocate

Finally, we need to delete the cursor definition and released all the system resources associated with the cursor.

Syntax to Declare Cursor

Declare Cursor SQL Comaand is used to define the cursor with many options that impact the scalablity and loading behaviour of the cursor. The basic syntax is given below

DECLARE cursor_name CURSOR [LOCAL | GLOBAL] --define cursor scope [FORWARD_ONLY | SCROLL] --define cursor movements (forward/backward) [STATIC | KEYSET | DYNAMIC | FAST_FORWARD] --basic type of cursor [READ_ONLY | SCROLL_LOCKS | OPTIMISTIC] --define locks FOR select_statement --define SQL Select statement FOR UPDATE [col1,col2,...coln] --define columns that need to be updated

Syntax to Open Cursor

A Cursor can be opened locally or globally. By default it is opened locally. The basic syntax to open cursor is given below:

OPEN [GLOBAL] cursor_name --by default it is local

Syntax to Fetch Cursor

Fetch statement provides the many options to retrieve the rows from the cursor. NEXT is the default option. The basic syntax to fetch cursor is given below:

FETCH [NEXT|PRIOR|FIRST|LAST|ABSOLUTE n|RELATIVE n]FROM [GLOBAL] cursor_name INTO @Variable_name[1,2,..n]

Syntax to Close Cursor

Close statement closed the cursor explicitly. The basic syntax to close cursor is given below:

Page 7: cursor

CLOSE cursor_name --after closing it can be reopen

Syntax to Deallocate Cursor

Deallocate statement delete the cursor definition and free all the system resources associated with the cursor. The basic syntax to close cursor is given below:

DEALLOCATE cursor_name --after deallocation it can't be reopen

SQL SERVER – Simple Examples of Cursors CREATE TABLE Employee( EmpID int PRIMARY KEY, EmpName varchar (50) NOT NULL, Salary int NOT NULL, Address varchar (200) NOT NULL,)GOINSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(1,'Mohan',12000,'Noida')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(2,'Pavan',25000,'Delhi')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(3,'Amit',22000,'Dehradun')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(4,'Sonu',22000,'Noida')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(5,'Deepak',28000,'Gurgaon')GOSELECT * FROM Employee

SET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50)DECLARE @salary int DECLARE cur_emp CURSORSTATIC FOR SELECT EmpID,EmpName,Salary from EmployeeOPEN cur_empIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM cur_emp INTO @Id,@name,@salary WHILE @@Fetch_status = 0 BEGIN PRINT 'ID : '+ convert(varchar(20),@Id)+', Name : '+@name+ ', Salary : '+convert(varchar(20),@salary) FETCH NEXT FROM cur_emp INTO @Id,@name,@salary

Page 8: cursor

ENDENDCLOSE cur_empDEALLOCATE cur_empSET NOCOUNT OFF

SQL Server Different Types of CursorsA Cursor allow us to retrieve data from a result set in singleton fashion means row by row. Cursor are required when we need to update records in a database table one row at a time. I have already explained the basic of cursor.

A Cursor impacts the performance of the SQL Server since it uses the SQL Server instances' memory, reduce concurrency, decrease network bandwidth and lock resources. Hence it is mandatory to understand the cursor types and its functions so that you can use suitable cursor according to your needs.

You should avoid the use of cursor. Basically you should use cursor alternatives like as WHILE loop, sub queries, Temporary tables and Table variables. We should use cursor in that case when there is no option except cursor.

Types of Cursors

1. Static Cursors

A static cursor populates the result set at the time of cursor creation and query result is cached for the lifetime of the cursor. A static cursor can move forward and backward direction. A static cursor is slower and use more memory in comparison to other cursor. Hence you should use it only if scrolling is required and other types of cursors are not suitable.

You can't update, delete data using static cursor. It is not sensitive to any changes to the original data source. By default static cursors are scrollable.

2. Dynamic Cursors

A dynamic cursor allows you to see the data updation, deletion and insertion in the data source while the cursor is open. Hence a dynamic cursor is sensitive to any changes to the data source and supports update, delete operations. By default dynamic cursors are scrollable.

Page 9: cursor

3. Forward Only Cursors

A forward only cursor is the fastest cursor among the all cursors but it doesn't support backward scrolling. You can update, delete data using Forward Only cursor. It is sensitive to any changes to the original data source.

There are three more types of Forward Only Cursors.Forward_Only KEYSET, FORWARD_ONLY STATIC and FAST_FORWARD.

A FORWARD_ONLY STATIC Cursor is populated at the time of creation and cached the data to the cursor lifetime. It is not sensitive to any changes to the data source.

A FAST_FORWARD Cursor is the fastest cursor and it is not sensitive to any changes to the data source.

4. Keyset Driven Cursors

A keyset driven cursor is controlled by a set of unique identifiers as the keys in the keyset. The keyset depends on all the rows that qualified the SELECT statement at the time of cursor was opened. A keyset driven cursor is sensitive to any changes to the data source and supports update, delete operations. By default keyset driven cursors are scrollable.

SQL SERVER – Examples of Cursors CREATE TABLE Employee( EmpID int PRIMARY KEY, EmpName varchar (50) NOT NULL, Salary int NOT NULL, Address varchar (200) NOT NULL,)GOINSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(1,'Mohan',12000,'Noida')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(2,'Pavan',25000,'Delhi')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(3,'Amit',22000,'Dehradun')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(4,'Sonu',22000,'Noida')INSERT INTO Employee(EmpID,EmpName,Salary,Address) VALUES(5,'Deepak',28000,'Gurgaon')GOSELECT * FROM Employee

Page 10: cursor

Static Cursor - Example SET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50)DECLARE @salary int DECLARE cur_emp CURSORSTATIC FOR SELECT EmpID,EmpName,Salary from EmployeeOPEN cur_empIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM cur_emp INTO @Id,@name,@salary WHILE @@Fetch_status = 0 BEGIN PRINT 'ID : '+ convert(varchar(20),@Id)+', Name : '+@name+ ', Salary : '+convert(varchar(20),@salary) FETCH NEXT FROM cur_emp INTO @Id,@name,@salary ENDENDCLOSE cur_empDEALLOCATE cur_empSET NOCOUNT OFF

Dynamic Cursor - Example --Dynamic Cursor for UpdateSET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50) DECLARE Dynamic_cur_empupdate CURSORDYNAMIC FOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Dynamic_cur_empupdateIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Dynamic_cur_empupdate INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN

Page 11: cursor

IF @name='Mohan' Update Employee SET Salary=15000 WHERE CURRENT OF Dynamic_cur_empupdate FETCH NEXT FROM Dynamic_cur_empupdate INTO @Id,@name ENDENDCLOSE Dynamic_cur_empupdateDEALLOCATE Dynamic_cur_empupdateSET NOCOUNT OFF GoSelect * from Employee

-- Dynamic Cursor for DELETESET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50) DECLARE Dynamic_cur_empdelete CURSORDYNAMIC FOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Dynamic_cur_empdeleteIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Dynamic_cur_empdelete INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN IF @name='Deepak' DELETE Employee WHERE CURRENT OF Dynamic_cur_empdelete FETCH NEXT FROM Dynamic_cur_empdelete INTO @Id,@name ENDENDCLOSE Dynamic_cur_empdeleteDEALLOCATE Dynamic_cur_empdeleteSET NOCOUNT OFFGoSelect * from Employee

Forward Only Cursor - Example --Forward Only Cursor for UpdateSET NOCOUNT ONDECLARE @Id int

Page 12: cursor

DECLARE @name varchar(50) DECLARE Forward_cur_empupdate CURSORFORWARD_ONLYFOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Forward_cur_empupdateIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Forward_cur_empupdate INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN IF @name='Amit' Update Employee SET Salary=24000 WHERE CURRENT OF Forward_cur_empupdate FETCH NEXT FROM Forward_cur_empupdate INTO @Id,@name ENDENDCLOSE Forward_cur_empupdateDEALLOCATE Forward_cur_empupdateSET NOCOUNT OFF GoSelect * from Employee

-- Forward Only Cursor for DeleteSET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50) DECLARE Forward_cur_empdelete CURSORFORWARD_ONLYFOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Forward_cur_empdeleteIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Forward_cur_empdelete INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN IF @name='Sonu' DELETE Employee WHERE CURRENT OF Forward_cur_empdelete FETCH NEXT FROM Forward_cur_empdelete INTO @Id,@name ENDENDCLOSE Forward_cur_empdeleteDEALLOCATE Forward_cur_empdeleteSET NOCOUNT OFF GoSelect * from Employee

Page 13: cursor

Keyset Driven Cursor - Example -- Keyset driven Cursor for UpdateSET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50) DECLARE Keyset_cur_empupdate CURSORKEYSETFOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Keyset_cur_empupdateIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Keyset_cur_empupdate INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN IF @name='Pavan' Update Employee SET Salary=27000 WHERE CURRENT OF Keyset_cur_empupdate FETCH NEXT FROM Keyset_cur_empupdate INTO @Id,@name ENDENDCLOSE Keyset_cur_empupdateDEALLOCATE Keyset_cur_empupdateSET NOCOUNT OFF GoSelect * from Employee

-- Keyse Driven Cursor for DeleteSET NOCOUNT ONDECLARE @Id intDECLARE @name varchar(50) DECLARE Keyset_cur_empdelete CURSORKEYSETFOR SELECT EmpID,EmpName from Employee ORDER BY EmpNameOPEN Keyset_cur_empdeleteIF @@CURSOR_ROWS > 0 BEGIN FETCH NEXT FROM Keyset_cur_empdelete INTO @Id,@name WHILE @@Fetch_status = 0 BEGIN IF @name='Amit' DELETE Employee WHERE CURRENT OF Keyset_cur_empdelete

Page 14: cursor

FETCH NEXT FROM Keyset_cur_empdelete INTO @Id,@name ENDENDCLOSE Keyset_cur_empdeleteDEALLOCATE Keyset_cur_empdeleteSET NOCOUNT OFF Go Select * from Employee

How cursors workTo visualize how a cursor works, think of the apparatus used by a skyscraper window washer to travel up and down the skyscraper, stopping at each floor to wash each window. For most cursor types, key data is brought into memory and the cursor navigates this key data on a row-by-row basis; similar to the window washer going floor by floor.

A cursor requires two operations. The placement operation moves the cursor to a row in the results set. The retrieve statement returns the data underlying that row, called a fetch. Set operations accomplish this with a single statement:

select * from TableName where pk=1

Keeping in mind the window-washer analogy, let's walk through the steps you would take in using a cursor.

Two sets of syntaxes are supported by cursors: SQL-92 and T-SQL Extended Syntax. For the most part I will look at the T-SQL Extended Syntax and reference the SQL-92 syntax for comparative purposes. There are no new cursor features in SQL Server 2005.

First you create the cursor using a declare statement, which involves setting the cursor options and specifying the results set.

Cursor options

There are four sets of cursor options:

STATICThe STATIC cursor copies the data in the results set into tempdb and DML that occurs in the underlying results fails to reflect in the cursor's data. Subsequent fetch statements are made on the results set in tempdb. This is perfect when the data underlying your cursor is static or your cursor has no real-time requirements. For example, most cursors Microsoft uses are declared as static as the operation being carried out on the data needs to be done on point-in-time requirements. In other words, it does not need to know about new rows -- the data is static.

Page 15: cursor

KEYSETThe KEYSET cursor is implemented by copying primary key data into tempdb. As the cursor moves through the result set, the cursor will see modifications to the underlying data but it will not see new rows. If data is fetched from a row that no longer exists, nulls will be returned and the @@FETCH_STATUS variable will have a value of -2. The order of the cursor data is also maintained. The KEYSET cursor is the default cursor type. Fetch statements are made on the underlying base tables based on the keyset data cached in tempdb. These cursors take more time to open than DYNAMIC cursors, but they also have fewer resource requirements.

DYNAMICThe DYNAMIC cursor is similar to the KEYSET cursor in that the cursor can see data modifications in the underlying base tables, but it can also see newly inserted and deleted rows. It does not preserve order, which can lead to the Halloween problem as illustrated in script 4. Fetch statements are made on the underlying base tables, based on the key data cached in tempdb, but the key data is refreshed with each modification to key data on the base tables. It is the most expensive cursor type to implement.

FAST_FORWARDA FAST_FORWARD cursor provides optimal performance but only supports the NEXT argument, which only fetches the next row. Other cursor types will be able to fetch the next row, the prior row (using the PRIOR command), the first row (using the FIRST argument), the last row using the LAST argument, the nth row (using the ABSOLUTE arguments), or leap ahead n rows from the current cursor location (using the RELATIVE argument).

The above cursor options control the following:

Scope or visibilityIs the cursor only visible within a batch (local) or beyond the batch (global)? Cursors are only visible within a connection.

ScrollabilityCan the fetch statement fetch only the next row, fetch in any direction and/or by a specific number of rows? The advantage of a forward-only cursor is that it performs faster than a cursor type that can move in any direction. The two options are FORWARD_ONLY and SCROLL (any number and in any direction).

MembershipWhich rows are members of your cursor? Can the cursor see changes happening in the underlying results set, and can it see newly inserted/deleted rows?

UpdatabilityCan you update or delete the rows in the underlying results set? To update the tables underlying your cursor, you must have the following:

1. A primary key on the base tables underlying your cursor to update them. Otherwise you will get the message:

Page 16: cursor

Server: Msg 16929, Level 16, State 1, Line 1The cursor is READ ONLY.The statement has been terminated.

2. A cursor defined as KEYSET, DYNAMIC or FAST_FORWARD.

3. The WHERE CURRENT syntax to update or delete a row. Please refer to this script for an illustration of cursor updatability functions.

You retrieve rows from the cursor using fetch statements. You should always check the value of the @@Fetch_Status variable to ensure that it has a value of 0. The @@Fetch_Status variable can have three values:

0 - row successfully returned-1- fetch statement has read beyond the number of rows in the cursor -2 - row no longer exists in your results set

The fetch statements are analogous to our window washers moving down the sides of the sky scraper. With the fetch statement, the logical operations are position and then retrieve; twice as many operations as a set statement (i.e., with a set statement it's INSERT, UPDATE or DELETE).

Finally, clean up after your cursor using the close MyCursorName statement and then deallocate its resources using the deallocation MyCursorName statement. Note that a quick way to return to the beginning of a FAST_FORWARD cursor is to close and reopen it.

xecute the following Microsoft SQL Server T-SQL example scripts in Management Studio Query Editor to demonstrate the construction of cursor and nested cursors logic.

SQL Server Cursor Performance: Cursor solutions do not scale well for large data sets. For such cases, it is better to use set-based operations. See set-based solution T-SQL script following the Purchase Order nested cursors demo.

-- SQL Server cursor example - row-by-row operation - DECLARE CURSOR

DECLARE @dbName sysname

DECLARE AllDBCursor CURSOR  STATIC LOCAL FOR

  SELECT   name FROM     MASTER.dbo.sysdatabases

  WHERE    name NOT IN ('master','tempdb','model','msdb') ORDER BY name

Page 17: cursor

OPEN AllDBCursor; FETCH  AllDBCursor INTO @dbName;

WHILE (@@FETCH_STATUS = 0) -- loop through all db-s 

  BEGIN

/***** PROCESSING (like BACKUP) db by db goes here - record-by-record process  *****/ 

    PRINT @dbName

    FETCH  AllDBCursor   INTO @dbName

  END -- while 

CLOSE AllDBCursor; DEALLOCATE AllDBCursor;

/* Messages

AdventureWorks

AdventureWorks2008

AdventureWorksDW

AdventureWorksDW2008

..... */

------------

-- T-SQL Cursor declaration and usage example - cursor loop syntax - using t-sql cursor

------------

USE AdventureWorks2008;

DECLARE curSubcategory CURSOR STATIC LOCAL               -- sql declare cursor

FOR SELECT ProductSubcategoryID, Subcategory=Name

FROM Production.ProductSubcategory ORDER BY Subcategory

DECLARE @Subcategory varchar(40), @PSID int

OPEN curSubcategory

FETCH NEXT FROM curSubcategory INTO @PSID, @Subcategory  -- sql fetch cursor

WHILE (@@fetch_status = 0)                    -- sql cursor fetch_status

Page 18: cursor

BEGIN -- begin cursor loop

/***** USER DEFINED CODE HERE - POSSIBLY NESTED CURSOR *****/

            DECLARE @Msg varchar(128)

            SELECT @Msg = 'ProductSubcategory info: ' + @Subcategory + ' '+

                   CONVERT(varchar,@PSID)

            PRINT @Msg

FETCH NEXT FROM curSubcategory INTO @PSID, @Subcategory   -- sql fetch cursor

END -- end cursor loop

CLOSE curSubcategory

DEALLOCATE curSubcategory

GO

/* Partial output in Messages

 

ProductSubcategory info: Bib-Shorts 18

ProductSubcategory info: Bike Racks 26

ProductSubcategory info: Bike Stands 27

ProductSubcategory info: Bottles and Cages 28

ProductSubcategory info: Bottom Brackets 5

ProductSubcategory info: Brakes 6

*/

------------

------------

-- T SQL Search All Text & XML Columns in All Tables

------------

-- SQL nested cursors - sql server nested cursor - transact sql nested cursor

Page 19: cursor

USE AdventureWorks;

GO

-- SQL Server create stored procedure with nested cursors

CREATE PROC sprocSearchKeywordInAllTables  @Keyword NVARCHAR(64)

AS

  BEGIN

    SET NOCOUNT  ON

    DECLARE  @OutputLength VARCHAR(4),

             @NolockOption CHAR(8)

         SET @OutputLength = '256'

         SET @NolockOption = ''

         -- SET @NolockOption =  '(NOLOCK)'

    DECLARE  @DynamicSQL   NVARCHAR(MAX),

             @SchemaTableName   NVARCHAR(256),

             @SchemaTableColumn NVARCHAR(128),

             @SearchWildcard    NVARCHAR(128)

         SET @SearchWildcard = QUOTENAME('%' + @Keyword + '%',CHAR(39)+CHAR(39))

         PRINT @SearchWildcard

    DECLARE  @SearchResults  TABLE(

                                   SchemaTableColumn NVARCHAR(384),

                                   TextWithKeyword   NVARCHAR(MAX)

                                   )

 

    DECLARE curAllTables CURSOR  STATIC LOCAL FOR

    SELECT   QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) AS ST

Page 20: cursor

    FROM     INFORMATION_SCHEMA.TABLES

    WHERE    TABLE_TYPE = 'BASE TABLE'

             AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(TABLE_SCHEMA) + '.'

             + QUOTENAME(TABLE_NAME)), 'IsMSShipped') != 1

    ORDER BY ST

    OPEN curAllTables

    FETCH NEXT FROM curAllTables

    INTO @SchemaTableName

    

    WHILE (@@FETCH_STATUS = 0) -- Outer cursor loop

      BEGIN

        PRINT @SchemaTableName

        SET @SchemaTableColumn = ''

        DECLARE curAllColumns CURSOR  FOR -- Nested cursor

        SELECT   QUOTENAME(COLUMN_NAME)

        FROM     INFORMATION_SCHEMA.COLUMNS

        WHERE    TABLE_NAME = PARSENAME(@SchemaTableName,1)

                 AND TABLE_SCHEMA = PARSENAME(@SchemaTableName,2)

                 AND DATA_TYPE IN ('varchar','nvarchar','char','nchar','xml')

        ORDER BY ORDINAL_POSITION

        OPEN curAllColumns

        FETCH NEXT FROM curAllColumns

        INTO @SchemaTableColumn

        WHILE (@@FETCH_STATUS = 0) -- Inner cursor loop (nested cursor while)

          BEGIN

            PRINT '  ' + @SchemaTableColumn

Page 21: cursor

            SET @DynamicSQL = 'SELECT ''' + @SchemaTableName + '.' +

              @SchemaTableColumn + ''', LEFT(CONVERT(nvarchar(max),' +

              @SchemaTableColumn + '),' + @OutputLength + ')  FROM ' +

              @SchemaTableName + ' '+@NolockOption+

              ' WHERE CONVERT(nvarchar(max),' + @SchemaTableColumn +

              ') LIKE ' + @SearchWildcard

            INSERT INTO @SearchResults

            EXEC sp_executeSQL  @DynamicSQL

            FETCH NEXT FROM curAllColumns

            INTO @SchemaTableColumn

          END  -- Inner cursor loop

        CLOSE curAllColumns

        DEALLOCATE curAllColumns

        FETCH NEXT FROM curAllTables

        INTO @SchemaTableName

      END  -- Outer cursor loop

    CLOSE curAllTables

    DEALLOCATE curAllTables

   

    SELECT DISTINCT SchemaTableColumn, TextWithKeyWord FROM   @SearchResults

  END

GO

 

EXEC sprocSearchKeywordInAllTables  'Hamilton'

EXEC sprocSearchKeywordInAllTables  'Sánchez'

EXEC sprocSearchKeywordInAllTables  'O''Donnell'

Page 22: cursor

EXEC sprocSearchKeywordInAllTables  'Certification'

------------

The following nested cursors consist of an outer cursor for purchase orders header info and an inner cursor for the details of each purchase order. It is an example for MS SQL nested cursor loop.

-------------- SQL Server Nested Cursors example - transact sql nested cursor

------------

-- SQL nested cursors - transact sql fetch_status - transact sql while loop

-- SQL nesting cursors - transact sql fetch next

-- T-SQL script for execution timing setup

USE AdventureWorks;

DBCC DROPCLEANBUFFERS

DECLARE @StartTime datetime

SET @StartTime = getdate()

 

-- Setup local variables

DECLARE     @IterationID INT,

            @OrderDetail VARCHAR(max),

            @ProductName VARCHAR(10)

 

-- Setup table variable

DECLARE @Result TABLE (PurchaseOrderID INT, OrderDetail VARCHAR(max))

 

 

-- OUTER CURSOR declaration - transact sql declare cursor

DECLARE curOrdersForReport CURSOR STATIC LOCAL FOR

Page 23: cursor

SELECT PurchaseOrderID

FROM Purchasing.PurchaseOrderHeader

WHERE Year(OrderDate) = 2004

  AND Month(OrderDate) = 2

ORDER BY PurchaseOrderID

 

OPEN curOrdersForReport

FETCH NEXT FROM curOrdersForReport INTO @IterationID

PRINT 'OUTER LOOP START'

 

WHILE (@@FETCH_STATUS = 0) -- sql cursor fetch_status

BEGIN

      SET @OrderDetail = ''

 

-- INNER CURSOR declaration - transact sql declare cursor

-- SQL Nested Cursor - sql cursor nested - cursor nesting

 

      DECLARE curDetailList CURSOR STATIC LOCAL FOR

      SELECT p.productNumber

      FROM Purchasing.PurchaseOrderDetail pd

      INNER JOIN Production.Product p

      ON pd.ProductID = p.ProductID

      WHERE pd.PurchaseOrderID = @IterationID

      ORDER BY PurchaseOrderDetailID

 

Page 24: cursor

      OPEN curDetailList

      FETCH NEXT FROM curDetailList INTO @ProductName

      PRINT 'INNER LOOP START'

 

      WHILE (@@FETCH_STATUS = 0)

      BEGIN

            SET @OrderDetail = @OrderDetail + @ProductName + ', '

            FETCH NEXT FROM curDetailList INTO @ProductName

            PRINT 'INNER LOOP'

      END -- inner while

 

      CLOSE curDetailList

      DEALLOCATE curDetailList

 

      -- Truncate trailing comma

      SET @OrderDetail = left(@OrderDetail, len(@OrderDetail)-1)

      INSERT INTO @Result VALUES (@IterationID, @OrderDetail)

 

      FETCH NEXT FROM curOrdersForReport INTO @IterationID

      PRINT 'OUTER LOOP'

END -- outer while

CLOSE curOrdersForReport

DEALLOCATE curOrdersForReport

 

-- Publish results

SELECT * FROM @Result ORDER BY PurchaseOrderID

Page 25: cursor

 

-- Timing result

SELECT ExecutionMsec = datediff(millisecond, @StartTime, getdate())

GO

-- 220 msecs

 

------------

-- Equivalent set-based operations solution

------------

 

-- Execution timing setup

DBCC DROPCLEANBUFFERS

DECLARE @StartTime datetime

SET @StartTime = getdate()

 

-- SQL comma-limited list generation

-- SQL nested select statement

-- SQL FOR XML PATH

SELECT

      poh.PurchaseOrderID,

      OrderDetail = Stuff((

-- SQL correlated subquery

      SELECT ', ' + ProductNumber as [text()]

      FROM Purchasing.PurchaseOrderDetail pod

      INNER JOIN Production.Product p

      ON pod.ProductID = p.ProductID

Page 26: cursor

      WHERE pod.PurchaseOrderID = poh.PurchaseOrderID

      ORDER BY PurchaseOrderDetailID

      FOR XML PATH ('')), 1, 1, '')

FROM Purchasing.PurchaseOrderHeader poh

WHERE Year(OrderDate) = 2004

  AND Month(OrderDate) = 2

ORDER BY PurchaseOrderID ;

 

-- Timing result

SELECT ExecutionMsec = datediff(millisecond, @StartTime, getdate())

GO

-- 110 msecs

 

 

/* Partial results

 

PurchaseOrderID   OrderDetail

1696              GT-0820, GT-1209

1697              HN-6320, HN-7161, HN-7162, HN-8320, HN-9161, HN-9168

1698              NI-4127

1699              RM-T801

1700              LI-1201, LI-1400, LI-3800

1701              TI-R982, TI-T723*/

 

Page 27: cursor

The following example uses @@FETCH_STATUS to control the WHILE loop in a typical cursor application:

-- T-SQL cursor declaration

DECLARE curManager CURSOR  FOR

SELECT EmployeeID,

       Title

FROM   AdventureWorks.HumanResources.Employee

WHERE  Title LIKE '%manager%'

        OR Title LIKE '%super%';

 

OPEN curManager;

FETCH NEXT FROM curManager;

WHILE @@FETCH_STATUS = 0

  BEGIN

    PRINT 'Cursor loop'

    FETCH NEXT FROM curManager;

  END; -- while

CLOSE curManager;

DEALLOCATE curManager;

GO

/* Partial results

 

EmployeeID        Title

3                 Engineering Manager

 

EmployeeID        Title

Page 28: cursor

6                 Marketing Manager

*/

 

However, the @@FETCH_STATUS is global to all cursors on a connection, therefore using @@FETCH_STATUS to control nested cursors may not be advisable. To play it safe for the case of following triple nested cursors demonstration, we avoid using @@FETCH_STATUS. Instead we order the SELECTs for the cursor and find the max value on one unique column. We use a comparison between the running values and maximum value to control the loop. The OUTER cursor loop is based on OrderDate. The MIDDLE cursor loop is based PurchaseOrderID-s received on a particular date. The INNER cursor loop is based on the products belonging to a particular PurchaseOrderID.

This is the entire triple nested cursors T-SQL script:

-- MSSQL nested cursors

USE AdventureWorks

GO

DECLARE  @DateIteration DATETIME,

         @IterationID   INT,

         @OrderDetail   VARCHAR(1024),

         @ProductNo     VARCHAR(10)

DECLARE  @MaxOrderDate DATETIME,

         @MaxPOID      INT,

         @MaxProdNo    VARCHAR(10)

DECLARE  @Result  TABLE(

                        OrderDate       DATETIME,

                        PurchaseOrderID INT,

                        OrderDetail     VARCHAR(1024)

                        )

Page 29: cursor

DECLARE curOrderDate CURSOR  FOR

SELECT   DISTINCT OrderDate

FROM     Purchasing.PurchaseOrderHeader

WHERE    year(OrderDate) = 2002

         AND month(OrderDate) = 7

ORDER BY OrderDate

 

SELECT @MaxOrderDate = OrderDate

FROM   Purchasing.PurchaseOrderHeader

WHERE  year(OrderDate) = 2002

       AND month(OrderDate) = 7

OPEN curOrderDate

FETCH NEXT FROM curOrderDate

INTO @DateIteration

PRINT 'OUTER LOOP'

WHILE (1 < 2)

  BEGIN

    DECLARE curOrdersForReport CURSOR  FOR

    SELECT   PurchaseOrderID

    FROM     Purchasing.PurchaseOrderHeader

    WHERE    OrderDate = @DateIteration

    ORDER BY PurchaseOrderID

    

    SELECT @MaxPOID = PurchaseOrderID

    FROM   Purchasing.PurchaseOrderHeader

    WHERE  OrderDate = @DateIteration

Page 30: cursor

    

    OPEN curOrdersForReport

    FETCH NEXT FROM curOrdersForReport

    INTO @IterationID

    PRINT 'MIDDLE LOOP'

    WHILE (1 < 2)

      BEGIN

        SET @OrderDetail = ''

        DECLARE curDetailList CURSOR  FOR

        SELECT   p.ProductNumber

        FROM     Purchasing.PurchaseOrderDetail pd

                 INNER JOIN Production.Product p

                   ON pd.ProductID = p.ProductID

        WHERE    pd.PurchaseOrderID = @IterationID

        ORDER BY p.ProductNumber

         

        SELECT @MaxProdNo = p.ProductNumber

        FROM   Purchasing.PurchaseOrderDetail pd

               INNER JOIN Production.Product p

                 ON pd.ProductID = p.ProductID

        WHERE  pd.PurchaseOrderID = @IterationID

        OPEN curDetailList

        FETCH NEXT FROM curDetailList

        INTO @ProductNo

        PRINT 'INNER LOOP'

        WHILE (1 < 2)

Page 31: cursor

          BEGIN

            SET @OrderDetail = @OrderDetail + @ProductNo + ', '

            IF (@ProductNo = @MaxProdNo)

              BREAK

            FETCH NEXT FROM curDetailList

            INTO @ProductNo

            PRINT 'INNER LOOP'

          END

         CLOSE curDetailList

         DEALLOCATE curDetailList

        

        INSERT INTO @Result

        VALUES     (@DateIteration,@IterationID,@OrderDetail)

        IF (@IterationID = @MaxPOID)

          BREAK

        FETCH NEXT FROM curOrdersForReport

        INTO @IterationID

        PRINT 'MIDDLE LOOP'

      END

    CLOSE curOrdersForReport

    DEALLOCATE curOrdersForReport

    IF (@DateIteration = @MaxOrderDate)

      BREAK

    FETCH NEXT FROM curOrderDate

    INTO @DateIteration

    PRINT 'OUTER LOOP'

Page 32: cursor

  END

CLOSE curOrderDate

DEALLOCATE curOrderDate

 

SELECT * FROM   @Result

GO

/* Messages (partial)

 

OUTER LOOP

MIDDLE LOOP

INNER LOOP

INNER LOOP

INNER LOOP

...

*/

Here is the result set:

OrderDate PurchaseOrderID OrderDetailJuly 1, 2002 157 HJ-3416, HJ-3816, HJ-3824, HJ-5161, HJ-5162, HJ-5811, July 1, 2002 158 BA-8327, July 1, 2002 159 AR-5381, July 1, 2002 160 HJ-3816, HJ-3824, HJ-5161, July 1, 2002 161 SP-2981, July 1, 2002 162 BE-2908, July 1, 2002 163 RM-R800, July 1, 2002 164 RM-T801, July 1, 2002 165 CA-5965, CA-6738, CA-7457, July 1, 2002 166 LI-1201, LI-1400, LI-3800, LI-5160, July 1, 2002 167 LJ-5811, LJ-5818, LJ-7161, LJ-7162, LJ-9080, LJ-9161, July 1, 2002 168 CB-2903, CN-6137, CR-7833, July 1, 2002 169 LN-3410, LN-3416, LN-3816, LN-3824, LN-4400, July 1, 2002 170 PD-T852, 

Page 33: cursor

July 1, 2002 171 CR-7833, July 1, 2002 172 RA-2345, 

July 13, 2002 173 PB-6109, July 13, 2002 174 CR-9981, July 13, 2002 175 SD-2342, SD-9872, July 13, 2002 176 PA-187B, PA-361R, PA-529S, PA-632U, 

July 24, 2002 177SE-M236, SE-M798, SE-M940, SE-R581, SE-R908, SE-R995, 

July 24, 2002 178 RF-9198, July 24, 2002 179 FC-3982, FL-2301, RC-0291, July 24, 2002 180 RM-M464, RM-M692, July 24, 2002 181 TP-0923, July 24, 2002 182 FC-3982, FL-2301, July 24, 2002 183 RM-M464, RM-M692, July 24, 2002 184 NI-9522, 

July 24, 2002 185FW-1000, FW-1200, FW-1400, FW-3400, FW-3800, FW-5160, FW-5800, FW-7160, FW-9160, 

July 24, 2002 186 PD-M282, PD-M340, 

July 24, 2002 187HN-3824, HN-4402, HN-5161, HN-5162, HN-5400, HN-5811, 

July 24, 2002 188 MS-1981, MS-2259, MS-2341, MS-2348, MS-6061, July 24, 2002 189 KW-4091, July 24, 2002 190 RM-R436, RM-R600, RM-R800, July 24, 2002 191 LE-5160, LE-6000, SE-T312, SE-T762, July 24, 2002 192 SH-4562, July 27, 2002 193 SH-9312, July 27, 2002 194 SE-M236, SE-M798, July 27, 2002 195 GT-0820, GT-1209, July 27, 2002 196 PD-M282, PD-M340, July 27, 2002 197 SD-9872, July 27, 2002 198 SE-R581, SE-R908, July 27, 2002 199 SE-M940, July 27, 2002 200 PD-M562,