Determine a cursor by condition - sql-server

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).

Related

Run a sql command against databases from table

Before I start, I need to point out that I am a SQL noob. I can write basic statements, but anything past JOIN statements is probably fairly new to me.
That said, I have cobbled together a script that deletes records from tables. The script itself does what it needs to do; however, when I run this script, I change the "USE" line to whatever database is next, stepping through databases manually. I use a command which populates a temporary table with a list of database names as reference.
How can I run my script against each database name in the temporary table directly, preferably all from a single stored procedure?
Well, one option is that you can use a cursor to grab all the database names (exclude databases you don't want to execute on), and use dynamic sql to execute for each database. It unfortunately has to be "dynamic" since you can't just do a while loop, USE #dbname /*your magic code*/ Fetch Next from MyCursor into #dbname... It errors out when trying to do USE #dbname, So you actually do have to use exec #variable in it.
DECLARE #dbname varchar(max)
DECLARE #executeme nvarchar(max)
DECLARE DBCursor CURSOR FOR
SELECT Name
FROM sys.databases
WHERE name not in ('master', 'tempdb', 'model', 'msdb'); --add additional exclusions here
OPEN DBCursor;
FETCH NEXT FROM DBCursor INTO #dbname;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #executeme =
N'use '+#dbname+'
--YOUR SCRIPT HERE
'
EXEC sp_executesql #executeme
FETCH NEXT FROM DBCursor into #dbname;
END;
CLOSE DBCursor;
DEALLOCATE DBCursor;
GO
This is going to loop across all databases, execute your script in each, until there is none left in the list of databases. You can exclude certain databases in the select statement for the cursor (like the master, etc.) and add in any additional logic as you see fit for excluding.
Additionally, you can implement this in a stored procedure, so all you have to do is run the stored procedure, sit back and drink your favorite drink while it does the heavy lifting for you ;)

How do I catch a specific exception type from within a stored procedure?

I am writing a stored procedure which iterates over all of the databases on the server and populates a table variable with an aggregate of the data from some of the different databases. Some databases I'm not interested in as they are irrelevant. The problem is when my CURSOR iterates through those databases I don't care about, a SELECT statement is issued on a table that doesn't exist. How can I ignore the Invalid object name exception and continue with my processing?
Edit:
Here is how I was attempting to skip over databases that were irrelevant:
DECLARE db_cursor CURSOR FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name NOT IN ('master','model','msdb','tempdb')
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #currentDatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'SELECT COUNT(Name) FROM ' + #currentDatabaseName + '.sys.Tables WHERE Name = ''SomeTableICareAbout'''
INSERT INTO #tableSearchResult
EXEC sp_executesql #sql
SET #tableCount = (SELECT COUNT(*) FROM #tableSearchResult WHERE TableCount = 1)
--If the table I care about was found, then do the good stuff
IF #tableCount > 0
...
The problem with this approach is if the executing user (in my case a service account) does not have access to SELECT on the table, then I never know about that error. If the user doesn't have SELECT access, I want that exception to be raised. But, even if the user doesn't have SELECT access, it can SELECT on the sys.Tables view.
You can't catch error 208 directly because it's a name resolution error that is raised at compilation time and before the code is actually executed. The behaviour is documented: see the section called "Errors Unaffected by a TRY…CATCH Construct" for an explanation, and the answers to this question have some interesting comments.
In addition to the 'solution' in the documentation, you can use dynamic SQL; the error will be caught in this example:
begin try
exec('select * from dbo.ThisTableDoesNotExist');
end try
begin catch
select error_number();
end catch;
If you're looping through all databases, there's a good chance you're using dynamic SQL somewhere anyway, so this might suit your case better.
You can catch the error if you are doing it inside a stored procedure (Example documented Here: http://msdn.microsoft.com/en-us/library/ms175976.aspx
Also you can change your dynamic sql to do something like this
SET #sql = '
If Exists(Select Name From ' + #currentDatabaseName + '.sys.Tables
WHERE Name = ''SomeTableICareAbout'')' --+
--Add Whatever the Good Stuff is
EXEC sp_executesql #sql
But checking if the table exists first, instead of doing the select count(1) from the table, will prevent that error from being raised.

SQL server cursor slow performance

I'm getting started with my first use of a cursor in a stored procedure in sql server 2008. I've done some preliminary reading and I understand that they have significant performance limitations. In my current case I think they're necessary (I want to run multiple stored procedures for each stock symbol in a symbols table.
Edit:
The sprocs I'll be calling on each symbol will for the most part be insert operations to calculate symbol- dependent values, such as 5 day moving average, average daily volume, ATR (average true range). Most of these values will be calculated from data from a daily pricing and volume table... I'd like to streamline the retrieval of data values that would be retrieved redundantly otherwise... for example, I'd like to get for each symbol the daily pricing and volume data into a table variable... that temp table will then be passed in to the stored procedure that calls each of the aggregated functions I just mentioned. Hope that makes sense...
So my initial "outer loop" cursor- based stored procedure is below.. it times out after several minutes, without returning anything to the output window.
ALTER PROCEDURE dbo.sprocSymbolDependentAggsDriver2
AS
DECLARE #symbol nchar(10)
DECLARE symbolCursor CURSOR
STATIC FOR
SELECT Symbol FROM tblSymbolsMain ORDER BY Symbol
OPEN symbolCursor
FETCH NEXT FROM symbolCursor INTO #symbol
WHILE ##FETCH_STATUS = 0
SET #symbol = #symbol + ': Test.'
FETCH NEXT FROM symbolCursor INTO #symbol
CLOSE symbolCursor
DEALLOCATE symbolCursor
When I run it without the #symbol local variable and eliminate the assignment to it in the while loop, it seems to run ok. Is there a clear violation of performance best- practices within that assignment? Thanks..
"In my current case I think they're necessary (I want to run multiple
stored procedures for each stock symbol in a symbols table."
Cursors are rarely necessary.
From your example above, I think a simple WHILE loop will easily take the place of your cursor. Adapted from SQL Cursors - How to avoid them (one of my favorite SQL bookmarks)
-- Create a temporary table...
CREATE TABLE #Symbols (
RowID int IDENTITY(1, 1),
Symbol(nvarchar(max))
)
DECLARE #NumberRecords int, #RowCount int
DECLARE #Symbol nvarchar(max)
-- Get your data that you want to loop over
INSERT INTO #Symbols (Symbol)
SELECT Symbol
FROM tblSymbolsMain
ORDER BY Symbol
-- Get the number of records you just grabbed
SET #NumberRecords = ##ROWCOUNT
SET #RowCount = 1
-- Just do a WHILE loop. No cursor necessary.
WHILE #RowCount <= #NumberRecords
BEGIN
SELECT #Symbol = Symbol
FROM #Symbols
WHERE RowID = #RowCount
EXEC <myProc1> #Symbol
EXEC <myProc2> #Symbol
EXEC <myProc3> #Symbol
SET #RowCount = #RowCount + 1
END
DROP TABLE #Symbols
You don't really need all that explicit cursor jazz to build a string. Here is probably a more efficient way to do it:
DECLARE #symbol NVARCHAR(MAX) = N'';
SELECT #symbol += ': Test.'
FROM dbo.tblSymbolsMain
ORDER BY Symbol;
Though I suspect you actually wanted to see the names of the symbol, e.g.
DECLARE #symbol NVARCHAR(MAX) = N'';
SELECT #symbol += N':' + Symbol
FROM dbo.tblSymbolsMain
ORDER BY Symbol;
One caveat is that while you will typically observe the order to be observed, it is not guaranteed. So if you want to stick to the cursor, at least declare the cursor as follows:
DECLARE symbolCursor CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
...
Also it seems to me like NCHAR(10) is not sufficient to hold the data you're trying to stuff into it, unless you only have one row (which is why I chose NVARCHAR(MAX) above).
And I agree with Abe... it is quite possible you don't need to fire a stored procedure for every row in the cursor, but to suggest ways around that (which will almost certainly be more efficient), we'd have to understand what those stored procedures actually do.
you need an begin end here:
WHILE ##FETCH_STATUS = 0 BEGIN
SET #symbol = #symbol + ': Test.'
FETCH NEXT FROM symbolCursor INTO #symbol
END
also try DECLARE symbolCursor CURSOR LOCAL READ_ONLY FORWARD_ONLY instead of STATIC to improve performance.
After reading all the suggestions, I ended up doing some old trick and it worked miracles!
I had this cursor which was taking almost 3 mins to run, while the enclosing query was instant. I have other databases with more complex cursors that were only taking 1 second or less, so I ruled out the global issue on using cursors. My solution:
Detach the database in question, but ensure you tick Update Statistics.
Attach the database and check performance
This seems to help optimize all the performance parameters without the detailed effort. I am using SQL Express 2008 R2.
Would like to know your experience.

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.

Best way to deploy user defined functions to multiple databases in SQL 2005

I manage a server with around 400+ databases which have the same database schema, i wish to deploy a custom clr/.net user defined function to them all, is there any easy way to do this, or must it be done individually to each database?
Best Regards,
Wayne
I think if you create it in master (in MSSQL) it can be referenced from any other db in that instance. Certainly seems to work for Stored Procs anyway.
I should add that this only works if the databases are all on the same server instance...
You could write a small app to deploy the udf to the master of each SQL server instance if all 400 reside on multiple servers.
I just find some way i think. Some kind of Inception ;)
USE data_base works only inner EXECUTE context, so...
DECLARE #sql NVARCHAR(max)
DECLARE #innersql NVARCHAR(max)
DECLARE c CURSOR READ_ONLY
FOR
SELECT name FROM sys.databases
DECLARE #name nvarchar(1000)
OPEN c
SET #innersql = 'CREATE FUNCTION Foo(#x varchar(1)) RETURNS varchar(100) AS ' +
' BEGIN RETURN(#x + ''''some text'''') END;'
-- ^^^^ ^^^^
-- every (') must be converted to QUAD ('''') instead of DOUBLE('') !!!
FETCH NEXT FROM c INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
-- create function must be the first statement in a query batch ???
-- ok, will be... in inner EXEC...
SET #sql = 'USE [' + #name + ']; EXEC (''' + #innersql + ''');'
--PRINT #sql
EXEC (#sql)
FETCH NEXT FROM c INTO #name
END
CLOSE c
DEALLOCATE c
Ups, I missed "CLR/.NET" part reading question. Sorry.
i'd just create a dynamic script to create it on each database. but after that i'd put it in the modal databases so that all new databases are created with it.
you could also use the same script to push out changes if the function ever gets modified.

Resources