Break from foreach loop in sql (dbo.sp_MsForEachDb) - sql

I have several databases with the same schema. All of the databases have a table called Invoices. Every invoice has a unique id because its a GUID. Sometimes I am given an invoice ID and I will like to know to what database it belongs. As a result I execute the following query:
DECLARE #sql NVARCHAR(2000)
SET #sql = '
IF OBJECT_ID(''[?].dbo.Invoices'') IS NOT NULL
begin
declare #query NVARCHAR(255)
Select #query = (Select [Id] from [?].dbo.Invoices where [Id] = ''XF4G-XF78-2156-7XH8'')
IF #query IS NOT NULL
begin
print ''Database = '' + ''?''
end
end
'
EXEC dbo.sp_MsForEachDb #sql
Basically EXEC dbo.sp_MsForEachDb #sql executes the #sql query replacing ? for each of the databases. Once I enter the print statement because an invoice was found I will like to stop execution. In other words I was hoping to be able to do something like
begin
print 'Database = ' + ''?''
return
end
If I debug my query I still see that the query gets executed on all databases even though I already found the invoice.

Unfortunately I don't think there is an easy way to do this.
A completely inelegant way would be to close the cursor that the system procedure uses. Instead of return use CLOSE hCForEachDatabase. It would kick you out of the loop, but with an error. Like I said, a horrible way of doing it.
A more elegant way would be to continue the loop through all the databases, but without doing any of your processing once a match has been found. Assuming the user has rights to create a temp table you could do something like this...
DECLARE #sql NVARCHAR(2000)
CREATE TABLE #StopProcess(STOP BIT NULL);
SET #sql = '
IF (SELECT COUNT(*) FROM #StopProcess) = 0
BEGIN
IF OBJECT_ID(''[?].dbo.Invoices'') IS NOT NULL
BEGIN
declare #query NVARCHAR(255)
Select #query = (Select [Id] from [?].dbo.Invoices where [Id] = ''XF4G-XF78-2156-7XH8'')
IF #query IS NOT NULL
END
print ''Database = '' + ''?''
INSERT INTO #StopProcess values (1);
END
END
END'
EXEC dbo.sp_MsForEachDb #sql
DROP TABLE #StopProcess;
If you absolutely need to stop processing through all the databases, the only alternative I can think of is to roll your own version of sp_MSforeachdb and sp_MSforeach_worker.
Hope this helps.

Related

Send query as parameter to SQL function

I want to create a SQL tabled-value function that will receive a query as n parameter through my API. In my function I want execute that query. The query will be a SELECT statement.
This is what I have done so far and what to achieve but it is not the correct way to do so.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS (
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200))
RETURNS TABLE
AS
RETURN
(
-- Execute query into a table
SELECT *
INTO #tableName
FROM (
EXEC(#query)
)
)
GO
Please suggest the correct way!
Try this one -
CREATE PROCEDURE dbo.sp_CUSTOM_EXPORT_RESULTS
#query NVARCHAR(MAX) = 'SELECT * FROM dbo.test'
, #guid UNIQUEIDENTIFIER
, #tableName VARCHAR(200) = 'test2'
AS BEGIN
SELECT #query =
REPLACE(#query,
'FROM',
'INTO [' + #tableName + '] FROM')
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
IF OBJECT_ID (N''' + #tableName + ''') IS NOT NULL
DROP TABLE [' + #tableName + ']
' + #query
PRINT #SQL
EXEC sys.sp_executesql #SQL
RETURN 0
END
GO
Output -
IF OBJECT_ID (N'test2') IS NOT NULL
DROP TABLE [test2]
SELECT * INTO [test2] FROM dbo.test
What I see in your question is encapsulation of:
taking a dynamic SQL expression
executing it to fill a parametrized table
Why do you want to have such an encapsulation?
First, this can have a negative impact on your database performance. Please read this on EXEC() and sp_executesql() . I hope your SP won't be called from multiple parts of your application, because this WILL get you into trouble, at least performance-wise.
Another thing is - how and where are you constructing your SQL? Obviously you do it somewhere else and it seems its manually created. If we're talking about a contemporary application, there are lot of OR/M solutions for this and manual construction of TSQL in runtime should be always avoided if possible. Not to mention EXEC is not guarding you against any form of SQL injection attacks. However, if all of this is a part of some database administration TSQL bundle, forget his paragraph.
At the end, if you want to simply load a new table from some existing table (or part of it) as a part of some administration task in TSQL, consider issuing a SELECT ... INTO ... This will create a new target table structure for you (omitting indexes and constraints) and copy the data. SELECT INTO will outperform INSERT INTO SELECT because SELECT INTO gets minimally logged.
I hope this will get you (and others) at least a bit on the right track.
You can use stored procedure as well, here is the code that you can try.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS
(
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200)
)
RETURNS TABLE
AS
RETURN
(
declare #strQuery nvarchar(max)
-- Execute query into a table
SET #strQuery = REPLACE(#query,'FROM', 'INTO '+#tableName+' FROM')
exec sp_executesql #strQuery
)
GO

How can I spot in what database is a stored procedure with name 'myStoredProcedure'?

There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END

How to tell if SQL stored in a variable will return any rows

If I have a SQL script stored in a variable like this:
DECLARE #SQL VARCHAR(MAX) = 'SELECT * FROM Employees WHERE Age > 80'
How can I tell if #SQL would return any rows if I were to run it?
In effect this:
IF EXISTS(#SQL) print 'yes, there are rows' -- Dummy code -- does not work!
I would like to try this without having to run the script in #SQL, insert that into a table and them count the rows.
Of course you need to run the script. To avoid having to insert the result into a table and count the rows you can use sp_executesql and an output parameter.
DECLARE #Statement NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Age > 80'
DECLARE #DynSQL NVARCHAR(max) = N'SET #Exists = CASE WHEN EXISTS(' +
#Statement +
N') THEN 1 ELSE 0 END'
DECLARE #Exists bit
EXEC sp_executesql #DynSQL,
N'#Exists bit OUTPUT',
#Exists = #Exists OUTPUT
SELECT #Exists AS [Exists]
While Martin's answer is also valid but can't we just use the ##RowCount after Executing the script? like
DECLARE #q nvarchar(max);
SET #q = 'declare #b int; select * from sys.tables where #b = 5';
EXEC (#q);
If ##RowCount > 0
Print 'Rows > 0';
Else
Print 'Rows = 0';
Note that the query has a variable declaration in it, which obviously cannot be used with Exists()
You can try an out-of-the-box solution.
For example. Keep track of a single variable called emp_over_80. Whenever you add an employee over that age, emp_over_80++. When you remove one, emp_over_80--
At the beginning of each day, run a query to determine the value of emp_over_80 (it may be an employee's birthday). Then, throughout the day, you can refer to emp_over_80 instead of re-running the SQL query.
Other options would be to keep the employee table sorted by age. If the last employee is over 80, then your query will return at least one row.
Now, many might say these are horrible coding practices, and I'd agree with them. But, I don't see another way to magically know the result (even a partial result) of a query before it runs.

IF EXISTS in SQL Server Cursor Not Working

I have a cursor which works fine but when it gets to this part of the script, it seems to still run the update even though the table doesn't exists:
SET #sql = 'IF (EXISTS (SELECT * FROM ps_vars_' + #datasetid + '))
BEGIN
UPDATE ps_vars_' + #datasetid + '
SET programming_notes = replace(programming_notes, ''Some of the variables listed are source variables.'')
END';
EXEC SP_EXECUTESQL #sql
What am I missing? The #datasetid variable gets passed in correctly too.
DECLARE #tablename sysname
SET #tablename = 'ps_vars' + #datasetid
IF (OBJECT_ID(#tablename, 'U') IS NOT NULL)
BEGIN
SET #sql = ' UPDATE ' + QUOTENAME(#tablename) + '
SET programming_notes = replace(programming_notes, ''Some of the variables listed are source variables.'') ';
EXEC sp_executesql #sql
END
When you use the EXISTS with the table name to see if the table exists you're actually trying to access the table - which doesn't exist. That's why you're getting an error, not because of your UPDATE statement.
Try this instead:
SET #sql = 'IF (OBJECT_ID(''ps_vars_' + #datasetid + ''') IS NOT NULL)
BEGIN
UPDATE ...
END'
Then think about what might be wrong with your database design that requires you to use dynamic SQL like this. Maybe your design is exactly how it needs to be, but in my experience 9 out of 10 times (probably much more) this kind of code is a symptom of a poor design.

MS SQL - High performance data inserting with stored procedures

Im searching for a very high performant possibility to insert data into a MS SQL database.
The data is a (relatively big) construct of objects with relations. For security reasons i want to use stored procedures instead of direct table access.
Lets say i have a structure like this:
Document
MetaData
User
Device
Content
ContentItem[0]
SubItem[0]
SubItem[1]
SubItem[2]
ContentItem[1]
...
ContentItem[2]
...
Right now I think of creating one big query, doing somehting like this (Just pseudo-code):
EXEC #DeviceID = CreateDevice ...;
EXEC #UserID = CreateUser ...;
EXEC #DocID = CreateDocument #DeviceID, #UserID, ...;
EXEC #ItemID = CreateItem #DocID, ...
EXEC CreateSubItem #ItemID, ...
EXEC CreateSubItem #ItemID, ...
EXEC CreateSubItem #ItemID, ...
...
But is this the best solution for performance? If not, what would be better?
Split it into more querys? Give all Data to one big stored procedure to reduce size of query? Any other performance clue?
I also thought of giving multiple items to one stored procedure, but i dont think its possible to give a non static amount of items to a stored procedure.
Since 'INSERT INTO A VALUES (B,C),(C,D),(E,F) is more performant than 3 single inserts i thought i could get some performance here.
Thanks for any hints,
Marks
One stored procedure so far as possible:
INSERT INTO MyTable(field1,field2)
SELECT "firstValue", "secondValue"
UNION ALL
SELECT "anotherFirstValue", "anotherSecondValue"
UNION ALL
If you aren't sure about how many items you're inserting you can construct the SQL query witin the sproc and then execute it. Here's a procedure I wrote to take a CSV list of groups and add their relationship to a user entity:
ALTER PROCEDURE [dbo].[UpdateUserADGroups]
#username varchar(100),
#groups varchar(5000)
AS
BEGIN
DECLARE #pos int,
#previous_pos int,
#value varchar(50),
#sql varchar(8000)
SET #pos = 1
SET #previous_pos = 0
SET #sql = 'INSERT INTO UserADGroups(UserID, RoleName)'
DECLARE #userID int
SET #userID = (SELECT TOP 1 UserID FROM Users WHERE Username = #username)
WHILE #pos > 0
BEGIN
SET #pos = CHARINDEX(',',#groups,#previous_pos+1)
IF #pos > 0
BEGIN
SET #value = SUBSTRING(#groups,#previous_pos+1,#pos-#previous_pos-1)
SET #sql = #sql + 'SELECT ' + cast(#userID as char(5)) + ',''' + #value + ''' UNION ALL '
SET #previous_pos = #pos
END
END
IF #previous_pos < LEN(#groups)
BEGIN
SET #value = SUBSTRING(#groups,#previous_pos+1,LEN(#groups))
SET #sql = #sql + 'SELECT ' + cast(#userID as char(5)) + ',''' + #value + ''''
END
print #sql
exec (#sql)
END
This is far faster than individual INSERTS.
Also, make sure you just a single clustered index on the primary key, more indexes will slow the INSERT down as they will need to update.
However, the more complex your dataset is, the less likely it is that you'll be able to do the above so you will simply have to make logical compromises. I actually end up calling the above routine around 8000 times.