Use of cursor in SQL - cursor

I want to know what is the use of cursor? i search on google and i read that cursor is used for manipulate data like in this example cursor is use .. in this example the select statement Select firstName, lastName FROM myTable returns rows
and when i execute whole query with cursor then this returns same as select statement return so what is the difference? we may use only select instead of cursor? here cursor what is the use ? can anyone explain please in simple words
DECLARE #fName varchar(50), #lName varchar(50)
DECLARE cursorName CURSOR -- Declare cursor
LOCAL SCROLL STATIC
FOR
Select firstName, lastName FROM myTable
OPEN cursorName -- open the cursor
FETCH NEXT FROM cursorName
INTO #fName, #lName
PRINT #fName + ' ' + #lName -- print the name
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM cursorName
INTO #fName, #lName
PRINT #fName + ' ' + #lName -- print the name
END
CLOSE cursorName -- close the cursor
DEALLOCATE cursorName -- Deallocate the cursor

Many DBA's and developers have love/hate relationship with Cursors. Some will tell you not to use it, others that there is no danger and you can use it. As with many other tools, a cursor is just another tool to be used on some specific scenarios, correctly used can be an awesome tool. But incorrectly used can cause big performance issues.
Cursors work on a row basis and are a perfect sample of the RBAR "Row By Agonizing Row" instead of sets-based operations where tsql shines. The sample you provided is a really bad sample of correct cursor usage, yes, it can show you how a cursor works, but as you commented, that same action can be done with a simple SELECT.
If you do a quick search on your prefered search engine will find lot of good references about cursors, here are some to read:
https://www.brentozar.com/sql-syntax-examples/cursor-example/
RBAR vs. Set based programming for SQL
https://www.red-gate.com/simple-talk/sql/t-sql-programming/rbar-row-by-agonizing-row/

Related

Can't query and put data inside a cursor when using variable inside the query

I have to put a result of a query (single column and value is being pulled) into a variable. I'm trying to use a cursor however I choose the database to query based on a variable here is my query
SELECT productName, price FROM #ShopName.dbo.Products WHERE ProductName = #ProductName
#ShopName variable is being pulled from the database first and assigned to the variable using a cursor. #ProductName variable is being populated by an input parameter coming from API. I have to get ProductName from a specific database (there are multiple databases with products), but the query above throws syntax errors. Additionally when I tried ad hoc query assigned to a variable:
SET #Sql = N'SELECT productName, price FROM ' + QUOTENAME(#ShopName) + '.dbo.Products WHERE ProductName = ' + #ProductName
It doesn't allow to use it in
DECLARE cursorT CURSOR
FOR
#Sql
This throws Incorrect syntax near '#Sql', Expecting '(', SELECT, or WITH
Is there any way to make it possible to use that query in cursor while using the variable with database name in it?
Cursors should be right at the bottom of your bag of techniques, used sparingly and with great care, only when necessary. I can't tell if it's necessary in your case, there's not enough code to know. But I wanted to get that out before continuing.
As a point of purely academic interest, yes, there are some ways you can do this. Two main ways:
Declare a cursor in the dynamic SQL, as Dale suggested. You can still use the cursor in static code which follows the declaration if the cursor is global.
Use dynamic SQL to drop the results into something with scope outside of the dynamic sql, like a temp table. The cursor over the temp table.
1 is just bad. It is likely to result in code which is extremely difficult to understand in future. I include it for curiosity only. 2 is reasonable.
Examples:
-- some dummy schema and data to work with
create table t(i int);
insert t values(1), (2);
-- option 1: declare a cursor dynamically, use it statically (don't do this)
declare #i int;
exec sp_executesql N'declare c cursor global for select i from t';
open c;
fetch next from c into #i;
while (##fetch_status = 0)
begin
print #i;
fetch next from c into #i;
end
close c;
deallocate c;
-- option 2: dynamically dump data to a table, eg a temp table
create table #u(i int);
exec sp_executesql N'insert #u (i) select i from t';
declare c cursor local for select i from #u;
declare #i int;
open c;
fetch next from c into #i;
while (##fetch_status = 0)
begin
print #i;
fetch next from c into #i;
end
close c;
deallocate c;

Adding the postfix "_DELETE" to all stored procedures identified as not being used

In attempting to clean up my database, I have managed to identity a list of stored procedures that aren't being used. I want to mark these for deletion, adding the post-fix "_DELETE" to all of these in one script. Can anyone advise me on how to go about this please? Thank you.
Try to use cursor for this purpose:
DECLARE #mockupTable TABLE(ID INT IDENTITY, SPName VARCHAR(100));
INSERT INTO #mockupTable VALUES
('old_proc_name1')
,('old_proc_name2')
,('old_proc_name3')
DECLARE #name VARCHAR(50) = 'deleted'
DECLARE #newName VARCHAR(50)
DECLARE db_cursor CURSOR FOR
SELECT SPName FROM #mockupTable
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #newName = CONCAT(#name, '_DELETED')
--PRINT #newname
EXEC sp_rename #name, #newname
FETCH NEXT FROM db_cursor INTO #name
END
CLOSE db_cursor
DEALLOCATE db_cursor
Assuming you already have the names for your procedures you can just substitute them into your cursor definition below:
DECLARE ProcCursor CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT Name = CONCAT(QUOTENAME(s.name), '.', QUOTENAME(p.name)),
[NewName] = CONCAT(p.name, '_DELETE')
FROM sys.procedures AS p
INNER JOIN sys.schemas AS s
ON s.schema_id = p.schema_id
WHERE p.object_id IN
( OBJECT_ID('dbo.SomeProc', 'P'),
OBJECT_ID('dbo.SomeProc2', 'P'),
OBJECT_ID('dbo.SomeProc3', 'P')
);
DECLARE #Name NVARCHAR(776), #NewName SYSNAME;
OPEN ProcCursor;
FETCH NEXT FROM ProcCursor INTO #Name, #NewName
WHILE (##FETCH_STATUS = 0)
BEGIN
EXECUTE sp_rename #Name, #NewName, 'OBJECT';
FETCH NEXT FROM ProcCursor INTO #Name, #NewName
END
CLOSE ProcCursor;
DEALLOCATE ProcCursor;
It is worth noting that this is one of the very few scenarios where I would advocate using a cursor, but as above when using a cursor you should always ensure you explicitly declare the simplest cursor possible (e.g. LOCAL STATIC READ_ONLY FORWARD_ONLY). By telling SQL Server your cursor will be static, only used locally, and only ever read and only in one direction, your cursor will be much faster than if you don't and SQL Server has to work on the assumption that anything could happen with the cursor. On a small scale like this it is unlikely to make a tangible difference, but on larger sets it can make a considerable difference.
For further reading see What impact can different cursor options have? - The conclusion is actually that I should have used LOCAL FAST_FORWARD, rather than the options I did use. I have left these in though, as the difference is negligible and I found using all 4 easier to remember and displays intent clearer.
To re-iterate what has been stated in comments, it really is a good idea to use version control on your databases, that way you don't need to mark anything for deletion, you can just delete it, and still retain the definition in your source control. If you can't use source control for whatever reason, DDL triggers can provide rudimentary change tracking

Could this cursor be optimized or rewritten for optimum performance?

There is a need to update all of our databases on our server and perform the same logic on each one. The databases in question all follow a common naming scheme like CorpDB1, CorpDB2, etc. Instead of creating a SQL Agent Job for each of the databases in question (over 50), I have thought about using a cursor to iterate over the list of databases and then perform some dynamic sql on each one. In light of the common notion that cursors should be a last resort; could this be rewritten for better performance or written another way perhaps with the use of the undocumented sp_MSforeachdb stored procedure?
DECLARE #db VARCHAR(100) --current database name
DECLARE #sql VARCHAR(1000) --t-sql used for processing on each database
DECLARE db_cursor CURSOR FAST_FORWARD FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name LIKE 'CorpDB%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #db
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'USE ' + #db +
' DELETE FROM db_table --more t-sql processing'
EXEC(#sql)
FETCH NEXT FROM db_cursor INTO #db
END
CLOSE db_cursor
DEALLOCATE db_cursor
Cursors are bad when they are used to tackle a set-based problem with procedural code. I don't think a cursor is necessarily a bad idea in your scenario.
When operations need to be run against multiple databases (backups, integrity checks, index maintenance, etc.), there's no issue with using a cursor. Sure, you could build a temp table that contains database names and loop through that...but it's still a procedural approach.
For your specific case, if you're not deleting rows in these tables based on some WHERE clause criteria, consider using TRUNCATE TABLE instead of DELETE FROM. Differences between the two operations explained here. Note that the user running TRUNCATE TABLE will need ALTER permission on the affected objects.
This will collect the set of delete statements and run them all in a single sequence. This is not necessarily going to be better performance-wise but just another way to skin the cat.
DECLARE #sql NVARCHAR(MAX); -- if SQL Server 2000, use NVARCHAR(4000)
SET #sql = N'';
SELECT #sql = #sql + N';DELETE ' + name + '..db_table -- more t-sql'
FROM master.sys.databases
WHERE name LIKE N'CorpDB%';
SET #sql = STUFF(#sql, 1, 1, '');
EXEC sp_executesql #sql;
You may consider building the string in a similar way inside your cursor instead of running EXEC() inside for each command. If you're going to continue using a cursor, use the following declaration:
DECLARE db_cursor CURSOR
LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR
This will have the least locking and no unnecessary tempdb usage.

While loop without a counter in SQL - help

I am running the following code. The code does not loop through all the records that are there in the #result. What is the correct syntax. Do I have to use counter in this case? I want it to be flexible so it print all values that are there in the variable.
declare #result varchar(1000)
select #result = item from items
while #result != ''
begin
print #result
end
Select item from items
result in this query
What I am getting from print is an endless loop that I have to manually stop and it prints this ...
small bed
small bed
small bed
small bed
What is decent way to print all values in the variable. I am talking about text data only, not numbers.
SET #result = ''
SELECT #result = #result + CHAR(10) + CHAR(13) + Item from Items
PRINT #Result
That's all you need. The WHILE loop is superfluous.
This might be difficult to explain, because it appears you're missing a fundamental understanding of the way SQL/databases work.
The code you wrote can only store a single value of the item column from the items table. Think of a varchar(1000) as the equivalent of a string in your favorite procedural language. The select #result = ... statement is essentially going to each row and setting #result to item, which means #result keeps getting replaced with the next value (and, upon conclusion of the statement, it's contents will be the last value in the table).
In general, you shouldn't be using loops in SQL code. That is best left for whatever front-end application needs the data. Of course, there are exceptions.
I would strongly suggest reading a primer on SQL, particularly on set-based operations vs procedural operations.
JNK's solution may give you what you're looking for in this instance, but I would question what exactly you would need that type of solution for in the first place.
You could use a cursor to iterate over all of the data returned from your query.
declare a_cursor cursor for select Item from Items
declare #Item varchar(1000)
open a_cursor
fetch next from a_cursor into #Item
while ##fetch_status = 0
begin
print #Item
fetch next from a_cursor into #Item
end
close a_cursor
deallocate a_cursor

Determine a cursor by condition

In SQL Server for CURSOR we say:
CREATE PROCEDURE SP_MY_PROC
(#BANKID VARCHAR(6)='')
-------------------------------
-------------------------------
DECLARE MY_CURSOR CURSOR FOR
SELECT .......
Now, what I wonder, can we determine the select statement according to a cerain condition?
IF BANKID<>''// SELECT * FROM EMPLOYESS WHERE BANKID=#BANKID to be the cursors query
ELSE // otherwise SELECT * FROM EMPLOYEES to be the cursors query
Or does it have to be static?
Yes, you can do this with Dynamic SQL
IF #BANKID<> ''
SET #sql = '
DECLARE MyCursor CURSOR FOR
SELECT ...'
ELSE
SET #sql = '
DECLARE MyCursor CURSOR FOR
SELECT ...'
EXEC sp_executesql #sql
OPEN MyCursor
If it is such a simple example, it's better to re-write it as a single query:
DECLARE MY_CURSOR CURSOR FOR
SELECT * FROM EMPLOYESS WHERE BANKID=#BANKID or #BANKID=''
And, of course, we haven't addressed whether a cursor is the right solution for the larger problem or not (cursors are frequently misused by people not used to thinking of set based solutions, which is what SQL is good at).
PS - avoid prefixing your stored procedures with sp_ - These names are "reserved" for SQL Server, and should be avoided to prevent future incompatibilities (and ignoring, for now, that it's also slower to access stored procs with such names, since SQL Server searches the master database before searching in the current database).

Resources