I want find the records which have ~ in the table.
I am using cursor to, but I am getting unexpected error.
Any help would be appreciated.
DECLARE #test VARCHAR(5000)
DECLARE #column_name VARCHAR(2000)
Declare #TABLE_NAME_MAIN VARCHAR(200)
SET #TABLE_NAME_MAIN = 'Ar_Receipt_Item_OHM'
DECLARE cur_name
CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TABLE_NAME_MAIN
OPEN cur_name
FETCH NEXT FROM cur_name INTO #column_name
WHILE ##Fetch_status = 0
BEGIN
SET #test = N'SELECT top 2 * FROM OHMPreStage.dbo.'+#TABLE_NAME_MAIN+' WHERE '+#column_name+' LIKE ''%~%'''
exec #test
FETCH NEXT FROM cur_name INTO #column_name
END
CLOSE cur_name
DEALLOCATE cur_name
SET NOCOUNT OFF
Error:
Msg 911, Level 16, State 4, Line 24
Database 'SELECT top 2 * FROM OHMPreStage' does not exist. Make sure that the name is entered correctly.
You just need to change:
exec #test
To
exec (#test)
EXEC is for executing stored procedures, EXEC() the function puts together a dynamic string and executes it.
You're probably going to have issues searching columns with "LIKE" That aren't string columns.
You should check out the answer to this question here, which gives you a script that scans all tables in a database for specific string content:
Selecting column names that have specified value
Just substitute your search string '%~%', and if you need to, limit the tables searched to just the one (or set of) tables you need.
Related
I'm using a search object query (found on the internet, wish I could give credit to the developer) to search database for the columns needed when I write queries. The output search object query allows me to enter the type of table to look in (partial name) as well as the column name (partial name) I'm trying to find. I've been attempting to modify the search object query so it returns the 1st value (Top 1) it finds. This would help me to easily see at a glance if the column has the particular type of data I'm looking for.
I've attempted to write it both as a stored procedure that I could pass two parameters (partial table and partial column name) and I've also tried using dynamic SQL (my first attempt at using it, so I'm a novice when it comes to use it). I had moderate success with the use of dynamic SQL, but can only get it to produce one result rather than be called multiple times for all the results in my search object output. The code I used is shown here:
-- This is the search object query found on internet
Use masterdb
Select a.name, b.name
From sysobjects a
Inner Join syscolumns b On a.id = b.id
Where b.name like '%Result%'
And a.name like '%Lab%'
Order By a.name, b.name
-- This is a separate query I used to test calling the data with dynamic SQL
DECLARE #value VARCHAR(100), #tablename VARCHAR(100)
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
If I use the search object query and search for tables that have lab and column names that have result, I might get output like this:
LabMain,ResultID
LabSpecimen,ResultCategory
LabSpecimen,ResultDate
LabSpecimen,Results
I would like to have the search object query pull data from the table in the first column and the column name in the 2nd column and return the first value it finds to give me a sample output for the given column name/table. Output would look like this:
LabMain,ResultID,E201812310001
LabSpecimen,ResultCategory,ExampleCategory
LabSpecimen,ResultDate,20181231
LabSpecimen,Results,34.20
Okay, I really didn't want to have to post an answer to this, but here goes.
So, the first, really-really-huge thing is: SQL Injection. SQL Injection is the #1 security vulnerability for something like a dozen years running, per OWASP. Basically, SQL Injection is where you use dynamic SQL that has any fragment of the sql command being populated by a user. So in the OP's case, this section here:
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
... if the end incarnation would be that #tableName and #value are populated by the user as part of their search? Then the user can do a 'search' that ends up injecting sql statements that the server runs directly; for a cheap example, imagine this for #value:
3' ; drop table #tableName --
... which would go ahead and drop every table that matches the #tablename you passed in.
Anyway, so, as we go through this problem, we're going to keep SQL Injection in mind at every step.
Problem #1: How to get the tables/columns that match.
You pretty much already nailed this. The only thing missing is to put it into a temp table so that you can loop through it (and limit it down to U-types, since otherwise you'll get stored procs and system tables.) I went ahead and had it also hit the Schema information - that way, if you have tables in different schemas, it'll still be able to get the results.
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
Now, notice that while #tableNameFragment and #columnNameFragment are used, they're not used in a dynamic query. It doesn't matter if the user puts in something malicious into those values
Problem #2 - How to loop through your table
Basically, you're going to need a cursor. I hate cursors, but sometimes (like this one), they're necessary.
Problem #3 - How to actually do a dynamic query and get a result back
This is actually trickier than it looks. You can't do a raw EXEC() for a return value, nor can you simply have the cmd you're executing populating a variable - because EXEC (and SP_ExecuteSql operate in a different context, so they can't populate variables outside in your script.)
You need to use SP_ExecuteSQL, but specify a return variable getting populated by the interior sql command. For example:
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select #retVal=1'
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
select #dynamicReturn
Problem #4 - How to write your Dynamic command
Here's where things get dicey, since it's where we're using a dynamic SQL command. The important thing here is: you cannot use anything the user provided as an input. Which means, you can't use the variables #tableNameFragment or #columnNameFragment. You can use the values in the #entityMatches table, though. Why? Because the user didn't populate them. They got populated by the data in the sys tables - it doesn't matter if the user puts something nefarious in the input variables, that #entityMatches data simply holds the existing table/column names that match.
Also important: When you're working on code that could be a problem if a future dev down the line tweaks or copies/pastes - you should put comment warnings to illuminate the issue.
So, putting it all together? You'll have something that looks like this:
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
declare #returnResults TABLE (TableName varchar(200), ColName varchar(128), FirstValue varchar(max))
declare Cur Cursor For select TableName,ColName from #entityMatches
declare #cursorTable varchar(200), #cursorColumn varchar(128)
open Cur
fetch Next from cur into #cursorTable,#cursorColumn
while ##FETCH_STATUS = 0
begin
-- Note: the variables #cursorTable, #cursorColumn are NOT user populated
-- but instead are populated from the Sys tables. Because of this,
-- this dynamic sql below is not SQL-Injection vulnerable (the entries
-- are not populated from user entry of any sort.)
-- Be very careful modifying the lines below to make sure you don't
-- introduce a vulnerability.
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select top 1 #retVal=[' + #cursorColumn + '] from ' + #cursorTable
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
insert into #returnResults values (#cursorTable, #cursorColumn, #dynamicReturn)
fetch Next from cur into #cursorTable,#cursorColumn
End
close cur
deallocate cur
select * from #returnResults
Create a stored procedure like below mention stored procedure.
Get the table and column name from sysobject & syscolumn and add it in hash table on the base of parameter of stored procedure. After that declare a cursor and in loop of cursor create a dynamic query of column and table name and get first row of current column from table of cursor loop. After that execute the query and update the result in the hash table. At the end of lookup select the Record from hash table. Check the below stored procedure. I hope that its helpful for you.
Create procedure Sp_GetSampleData
#TName varchar(200) = ''
as
Select
a.name TableName, b.name ColumnName,
CAST('' as varchar(max)) as SampleValue
into
#Tbl
from
sysobjects a
inner join
syscolumns b on a.id = b.id
where
(#TName='' or a.name = #TName)
order ny
a.name, b.name
declare #TableName varchar(200), #ColumnName varchar(200),
#sqlText nvarchar(max), #Val varchar(max)
declare Cur Cursor For
select TableName, ColumnName
from #Tbl
open Cur
fetch Next from cur into #TableName,#ColumnName
while ##FETCH_STATUS =0
begin
set #sqlText=''
set #Val=''
SET #sqlText = N'SELECT Top 1 #Val=[' + #ColumnName + '] FROM testndb.dbo.' + #TableName
EXEC Sp_executesql
#sqlText,
N'#Val varchar(max) output',
#Val output
print #sqlText
update #Tbl set SampleValue=#Val where TableName=#TableName and ColumnName =#ColumnName
fetch Next from cur into #TableName,#ColumnName
End
close cur
deallocate cur
select * from #Tbl
I have multiple databases in which I am looking for a particular column called Countries. If the column exists then I check the space characters in the column. If I loop through a cursor, the DB which is not having a Countries column will throw an error. How can I handle this error?
Concern: the catch block is not handling, please help me how to resolve the issue.
Query as shown below,
CREATE PROCEDURE [dbo].[USP_SMSGeneric_CountrySpace] #DB VARCHAR(100)
As
BEGIN
SET NOCOUNT ON
DECLARE #StudyID varchar(max)
DECLARE #Databasename VARCHAR(max)
DECLARE #QUERY NVARCHAR(MAX)
DECLARE #Protocol varchar(max)
DECLARE #Servername varchar(max)
DECLARE #script VARCHAR(Max)
DECLARE #script1 VARCHAR(Max)
DECLARE #initscript NVARCHAR(Max)
DECLARE #Countries VARCHAR(Max)
DECLARE #Countryrelease VARCHAR(Max)
IF OBJECT_ID('TEMPDB..#OBJMISSING') IS NOT NULL DROP TABLE #OBJMISSING
CREATE TABLE #OBJMISSING (ERRID INT IDENTITY(1,1),ERRNUM BIGINT,ERRMSG VARCHAR(MAX),DBNAME VARCHAR(MAX))
SET #initscript='
DECLARE csrStudy CURSOR FOR
SELECT ProtocolName, DBName, studyid,DBServer AS Servername from SMSAPP.dbo.studymaster WITH (NOLOCK)
WHERE ClientName LIKE ''%NOVARTIS%'' AND studystatus IN (1,2) AND DBServer IN (''SQL002'' ,''SQL004'',''SQL005'')
'
EXEC sp_executesql #initscript
OPEN csrStudy
FETCH NEXT FROM csrStudy INTO #Protocol,#Databasename,#StudyID,#ServerName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #DB = #Servername+'.'+#Databasename
SET #script = '
DECLARE #StrValue VARCHAR(max)
BEGIN TRY
IF EXISTS (
SELECT DISTINCT 1 FROM '+#DB+'.sys.columns c JOIN '+#DB+'.sys.Tables t ON c.Object_ID=t.Object_ID
WHERE c.Name = ''Countries’'' AND t.name =''tblMaterials'')
BEGIN
SELECT #StrValue = ISNULL(#StrValue + '','', '''') + Countries’ FROM (
SELECT DISTINCT (LEN(Countries’ + '','') - LEN(REPLACE(Countries’, '' '', '''') + '',''))CNT,Countries
FROM '+#DB+'.dbo.tblMaterials WITH (NOLOCK) )A WHERE CNT>0
END
END TRY
BEGIN CATCH
INSERT INTO #OBJMISSING VALUES
(ERROR_NUMBER(),ERROR_MESSAGE(),''+#Databasename+'')
END CATCH
IF #StrValue IS NOT NULL -- If any Duplicate values found, then raise an alert
BEGIN
SELECT '+#StudyID+' As StudyID,
''Countries field value Should not have space'' AS Actual ,
''Countries field value exists with space for String :'' + #StrValue AS Discrepancy INTO #tempOutput
I'm getting the following error:
The OLE DB provider "SQLNCLI10" for linked server "SQL001" does not
contain the table ""RAW."dbo"."tblMaterials"". The table either does
not exist or the current user does not have permissions on that table
You need a TRY CATCH outside the dynamic SQL.
The error message that is displayed is at parse time, before it even executes (for that EXEC statement). At this moment the engine validates that the tables and objects exist and if not then an error is returned. The execution never starts so it will never get to the CATCH section. This is why the TRY CATCH needs to be outside the dynamic SQL, because the whole dynamic SQL will get rejected after parsing.
The error message you are getting is coming from a query like the following:
EXEC('SELECT * FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable')
Msg 7314, Level 16, State 1, Line 1 The OLE DB provider "SQLNCLI11"
for linked server "SomeLinkedServer" does not contain the table
""DatabaseName"."SchemaName"."NonExistingTable"". The table either does not exist or
the current user does not have permissions on that table.
If you can wrap this on a TRY CATCH, then the control flow will jump to the catch since the severity of the error is high enough:
BEGIN TRY
EXEC('SELECT * FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable')
END TRY
BEGIN CATCH
SELECT 'This is the catch section'
END CATCH
Please note the difference against this following example, without dynamic SQL:
BEGIN TRY
SELECT 1 FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable
END TRY
BEGIN CATCH
SELECT 1
END CATCH
Msg 208, Level 16, State 1, Line 3 Invalid object name
'DatabaseName.SchemaName.NonExistingTable'.
This is because the whole batch is being rejected after parsing, so it can't jump to a CATCH as it never started execution. When you use dynamic SQL, the parse, compile and execute of the dynamic portion happens at the EXEC point (and that is exactly why it's dynamic), delaying the error throw so it can be caught.
I can't supply the full fixed code because what you posted isn't complete. But you should be able to ignore errors if you follow this guideline:
DECLARE #Variable ...
DECLARE MyCursor CURSOR FOR ...
FETCH NEXT FROM MyCursor INTO #Variable
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DECLARE #DynamicSQL VARCHAR(MAX) = ... -- The DynamicSQL may have another TRY CATCH inside
EXEC(#DynamicSQL)
END TRY
BEGIN CATCH
-- Do your catch operation here, you can leave this section empty if you want (not recommended)
END CATCH
FETCH NEXT FROM MyCursor INTO #Variable
END
I'm building a fun stored procedure that will use dynamic SQL, sp_executesql with parameters, to allow some alter statements for a column in all database tables if the column name exists ( As you can see I used a cursor for loop all the tables on DB)
I built a test but the parameter doesn't work, I get the next error on each alter table statement that runs
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '#parTablename'.
The next is the code
SET NOCOUNT ON;
GO
DECLARE #tablename varchar(100);
DECLARE #alteredColumn varchar(100)='[mycolumn] [datetimeoffset](0) NOT NULL;';
DECLARE #column varchar(100)='mycolumn';
DECLARE #parDefinition nvarchar(500) = N'#parTablename nvarchar(100)';
DECLARE #sqlCommand nvarchar(1000)= N'ALTER TABLE #parTablename ALTER COLUMN '+#alteredColumn;
DECLARE ALTERCURSOR CURSOR LOCAL FAST_FORWARD FOR
SELECT name AS tablename
FROM sys.Tables
OPEN ALTERCURSOR;
FETCH NEXT FROM ALTERCURSOR INTO #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
--print #tablename
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tablename AND COLUMN_NAME = #column)
BEGIN
EXECUTE sp_executesql #sqlCommand, #parDefinition,#parTablename = #tablename
END
FETCH NEXT FROM ALTERCURSOR INTO #tablename
END
CLOSE ALTERCURSOR;
DEALLOCATE ALTERCURSOR;
SET NOCOUNT OFF;
GO
SOLUTION
Apparently is not possible to send a table name as a parameter, instead of that I used the #SeanLange option for degub with a little modification
SET #sqlCommand =Replace(#sqlCommand, '#parTablename',QUOTENAME(#tablename))
EXECUTE sp_executesql #sqlCommand
You can't stick a parameter in the middle of your dynamic sql like this. You need to use PRINT instead of EXECUTE to debug this. I wouldn't use a cursor for this myself but if you go that path you will have to do something like this before the EXECUTE statement.
Set #sqlCommand = Replace(sqlCommand, '#parTablename', #parTablename)
I'm working with a customer who somehow loaded lowercase item numbers into a series of SQL tables. This should not be possible in this particular application and is causing all sorts of issues. I set out to update the items to the upper case versions one at a time but quickly realized that this would take forever. So I did the unthinkable and tried to use a cursor to help me out, however I keep stumbling over an error that has me baffled. Here's my code:
declare #tablename varchar(10)
declare upper_case cursor for
SELECT sys.objects.NAME
FROM sys.columns
INNER JOIN sys.objects ON sys.columns.object_id = sys.objects.object_id
WHERE sys.columns.NAME = 'item'
AND sys.objects.type = 'U'
OPEN upper_case
FETCH NEXT FROM upper_case into #tablename
UPDATE #tablename
SET item = upper(item)
CLOSE upper_case
DEALLOCATE upper_case
And here's the error:
Msg 1087, Level 16, State 1, Line 13
Must declare the table variable "#tablename".
I'm not using #tablename as a table variable, I'm trying to use it as a scalar variable but I thought, what the heck, I'll bite. So I switched it to a table variable:
declare #tablename table (tablename varchar(10))
And then I get this error:
Msg 137, Level 16, State 1, Line 5
Must declare the scalar variable "#tablename".
What am I missing? Am I not allowed to use a variable in an UPDATE statement? I know that each UPDATE can only update one table, but I thought that by using the cursor, I was effectively issuing multiple updates, each which only updates one table. Did I misunderstand that?
Here's the result that worked. I can't believe I'm the first to want to do this and I doubt I'll be the last:
DECLARE #tablename varchar(10)
DECLARE #sql nvarchar(max)
declare upper_case cursor for
SELECT sys.objects.NAME
FROM sys.columns
INNER JOIN sys.objects ON sys.columns.object_id = sys.objects.object_id
WHERE sys.columns.NAME = 'item'
AND sys.objects.type = 'U'
OPEN upper_case
FETCH upper_case into #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'UPDATE [' + #tablename + '] SET item = upper(item)'
EXEC(#sql)
FETCH upper_case into #tablename
END
CLOSE upper_case
DEALLOCATE upper_case
UPDATE you cannot pass table name as variable like you try:
UPDATE #tablename
SET item = upper(item);
Msg 1087, Level 16, State 1, Line 13 Must declare the table variable
"#tablename"
But UPDATE can work with table variables DECLARE #t TABLE = ... (not your case).
To use it how you want you need Dynamic-SQL like:
EXEC('UPDATE [' + #tablename + '] SET item = UPPER(item)');
As lad2025 points out, you need to use Dynamic SQL techniques to accomplish what you're trying to do. Two other points:
First, you're only updating the first result your cursor returns. You need to use a while loop to iterate through the cursor results, like so:
....
FETCH NEXT FROM upper_case into #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
<do dynamic update>
FETCH NEXT FROM upper_case into #tablename
END
<close & deallocate>
Second, I would really encourage use of the INFORMATION_SCHEMA views rather than directly querying system tables. For your purposes they provide more than enough information, and are more readable and stable across SQL versions to boot.
The system metadata in INFORMATION_SCHEMA is a SQL-92 standard. It's stable across MS-SQL version and other ANSI-SQL-compliant engines. See also: INFORMATION_SCHEMA vs sysobjects. The main downside for writing quick scripts is that you have to type "information_schema" instead of just "sys". Eventually muscle-memory kicks in :)
i want to ensure that all stored procedures are still syntactically valid. (This can happen if someone renames/deletes a table/column).
Right now my solution to check the syntax of all stored procedures is to go into Enterprise Manager, select the first stored procedure in the list, and use the procedure:
Enter
Alt+C
Escape
Escape
Down Arrow
Goto 1
It works, but it's pretty tedious. i'd like a stored procedure called
SyntaxCheckAllStoredProcedures
like the other stored procedure i wrote that does the same thing for views:
RefreshAllViews
For everyone's benefit, RefreshAllViews:
RefreshAllViews.prc
CREATE PROCEDURE dbo.RefreshAllViews AS
-- This sp will refresh all views in the catalog.
-- It enumerates all views, and runs sp_refreshview for each of them
DECLARE abc CURSOR FOR
SELECT TABLE_NAME AS ViewName
FROM INFORMATION_SCHEMA.VIEWS
OPEN abc
DECLARE #ViewName varchar(128)
-- Build select string
DECLARE #SQLString nvarchar(2048)
FETCH NEXT FROM abc
INTO #ViewName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQLString = 'EXECUTE sp_RefreshView '+#ViewName
PRINT #SQLString
EXECUTE sp_ExecuteSQL #SQLString
FETCH NEXT FROM abc
INTO #ViewName
END
CLOSE abc
DEALLOCATE abc
For everyone's benefit, a stored procedure to mark all stored procedure as needing a recompile (marking a stored procedure for recompile will not tell you if it's syntactically valid):
RecompileAllStoredProcedures.prc
CREATE PROCEDURE dbo.RecompileAllStoredProcedures AS
DECLARE abc CURSOR FOR
SELECT ROUTINE_NAME
FROM INFORMATION_SCHEMA.routines
WHERE ROUTINE_TYPE = 'PROCEDURE'
OPEN abc
DECLARE #RoutineName varchar(128)
-- Build select string once
DECLARE #SQLString nvarchar(2048)
FETCH NEXT FROM abc
INTO #RoutineName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQLString = 'EXECUTE sp_recompile '+#RoutineName
PRINT #SQLString
EXECUTE sp_ExecuteSQL #SQLString
FETCH NEXT FROM abc
INTO #RoutineName
END
CLOSE abc
DEALLOCATE abc
For completeness sake, the UpdateAllStatistics procedure. This will update all statistics in the database by doing a full data scan:
RefreshAllStatistics.prc
CREATE PROCEDURE dbo.RefreshAllStatistics AS
EXECUTE sp_msForEachTable 'UPDATE STATISTICS ? WITH FULLSCAN'
You can also do this "in-place" - without getting all the create statements.
In addition to setting NOEXEC ON, you will also need to set your favorite SHOWPLAN_* ON (I use SHOWPLAN_TEXT). Now you can get rid of your step 2 and just execute each procedure you retrieved in step 1.
Here is a sample using an individual stored procedure. You can work it into your favorite loop:
create procedure tests #bob int as
select * from missing_table_or_view
go
set showplan_text on;
go
set noexec on
exec tests
set noexec off
go
set showplan_text off;
go
drop procedure tests
go
The above sample should generate the following output:
Msg 208, Level 16, State 1, Procedure tests, Line 2
Invalid object name 'missing_table_or_view'.
The check suggested by KenJ is definitely the best one, since the recreate/alter-approaches does not find all errors. E.g.
impossible execution plans due to query-hints
I even had an SP referencing a non-existing table that went through without the error being detected.
Please find my version that checks all existing SPs at once with KenJ's method below. AFAIK, it will detect every error that will keep the SP from being executed.
--Forces the creation of execution-plans for all sps.
--To achieve this, a temporary SP is created that calls all existing SPs.
--It seems like the simulation of the parameters is not necessary. That makes things a lot easier.
DECLARE #stmt NVARCHAR(MAX) = 'CREATE PROCEDURE pTempCompileTest AS ' + CHAR(13) + CHAR(10)
SELECT #stmt = #stmt + 'EXEC [' + schemas.name + '].[' + procedures.name + '];'
FROM sys.procedures
INNER JOIN sys.schemas ON schemas.schema_id = procedures.schema_id
WHERE schemas.name = 'dbo'
ORDER BY procedures.name
EXEC sp_executesql #stmt
GO
--Here, the real magic happens.
--In order to display as many errors as possible, XACT_ABORT is turned off.
--Unfortunately, for some errors, the execution stops anyway.
SET XACT_ABORT OFF
GO
--Showplan disables the actual execution, but forces t-sql to create execution-plans for every statement.
--This is the core of the whole thing!
SET SHOWPLAN_ALL ON
GO
--You cannot use dynamic SQL in here, since sp_executesql will not be executed, but only show the string passed in in the execution-plan
EXEC pTempCompileTest
GO
SET SHOWPLAN_ALL OFF
GO
SET XACT_ABORT ON
GO
--drop temp sp again
DROP PROCEDURE pTempCompileTest
--If you have any errors in the messages-window now, you should fix these...
If you are using sql 2008 r2 or below then do not use
SET NOEXEC ON
It only checks the syntax and not for potential errors like the existence of tables or columns.
Instead use:
SET FMTONLY ON
it will do a full compile as it tries to return the meta data of the stored procedure.
For 2012 and you will need to use stored procedure:
sp_describe_first_result_set
Also you can do a complete script in Tsql that checks all sp and views, its just a bit of work.
UPDATE
I wrote a complete solution for in tsql that goes through all user defined stored proceedures and checks there syntax. the script is long winded but can be found here http://chocosmith.wordpress.com/2012/12/07/tsql-recompile-all-views-and-stored-proceedures-and-check-for-error/
In addition you might want to consider using Visual Studio Team System 2008 Database Edition which, among other things, does a static verification of all stored procedures in the project on build, thus ensuring that all are consistent with the current schema.
I know this is way old, but I created a slightly different version that actually re-creates all stored procedures, thus throwing errors if they cannot compile. This is something you do not achieve by using the SP_Recompile command.
CREATE PROCEDURE dbo.UTL_ForceSPRecompilation
(
#Verbose BIT = 0
)
AS
BEGIN
--Forces all stored procedures to recompile, thereby checking syntax validity.
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SPName NVARCHAR(255)
DECLARE abc CURSOR FOR
SELECT NAME, OBJECT_DEFINITION(o.[object_id])
FROM sys.objects AS o
WHERE o.[type] = 'P'
ORDER BY o.[name]
OPEN abc
FETCH NEXT FROM abc
INTO #SPName, #SQL
WHILE ##FETCH_STATUS = 0
BEGIN
--This changes "CREATE PROCEDURE" to "ALTER PROCEDURE"
SET #SQL = 'ALTER ' + RIGHT(#SQL, LEN(#SQL) - (CHARINDEX('CREATE', #SQL) + 6))
IF #Verbose <> 0 PRINT #SPName
EXEC(#SQL)
FETCH NEXT FROM abc
INTO #SPName, #SQL
END
CLOSE abc
DEALLOCATE abc
END
I know this is a old question but this is my solution when I could not find any suiting.
I required to validate my stored procedures and views after alot of changes in the database.
Basicly what i wanted was to try to do a ALTER PROCEDURE and ALTER VIEW using the current procedures and view (not actually changing them).
I have written this that works fairly well.
Note! Do not perform on live database, make a copy to validate and then fix the things need fixing. Also sys.sql_modules can be inconsistent so take extra care. I do not use this to actually make the changes, only to check which are not working properly.
DECLARE #scripts TABLE
(
Name NVARCHAR(MAX),
Command NVARCHAR(MAX),
[Type] NVARCHAR(1)
)
DECLARE #name NVARCHAR(MAX), -- Name of procedure or view
#command NVARCHAR(MAX), -- Command or part of command stored in syscomments
#type NVARCHAR(1) -- Procedure or view
INSERT INTO #scripts(Name, Command, [Type])
SELECT P.name, M.definition, 'P' FROM sys.procedures P
JOIN sys.sql_modules M ON P.object_id = M.object_id
INSERT INTO #scripts(Name, Command, [Type])
SELECT V.name, M.definition, 'V' FROM sys.views V
JOIN sys.sql_modules M ON V.object_id = M.object_id
DECLARE curs CURSOR FOR
SELECT Name, Command, [Type] FROM #scripts
OPEN curs
FETCH NEXT FROM curs
INTO #name, #command, #type
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
IF #type = 'P'
SET #command = REPLACE(#command, 'CREATE PROCEDURE', 'ALTER PROCEDURE')
ELSE
SET #command = REPLACE(#command, 'CREATE VIEW', 'ALTER VIEW')
EXEC sp_executesql #command
PRINT #name + ' - OK'
END TRY
BEGIN CATCH
PRINT #name + ' - FAILED: ' + CAST(ERROR_NUMBER() AS NVARCHAR(MAX)) + ' ' + ERROR_MESSAGE()
--PRINT #command
END CATCH
FETCH NEXT FROM curs
INTO #name, #command, #type
END
CLOSE curs
A bit of a drawn-out option:
Create a copy of the database
(backup and restore). You could do this on the target database, if your confidence level is high.
Use SSMS to script out all the
stored procedures into a single script file
DROP all the procedures
Run the script to recreate them. Any that can't be created will error out.
Couple of fussy gotchas in here, such as:
You want to have the "if proc exists
then drop proc GO create proc ... GO"
syntax to separte each procedure.
Nested procedures will fail if they
call a proc that has not yet been
(re)created. Running the script several
times should catch that (since
ordering them properly can be a real
pain).
Other and more obscure issues might crop up, so be wary.
To quickly drop 10 or 1000 procedures, run
SELECT 'DROP PROCEDURE ' + schema_name(schema_id) + '.' + name
from sys.procedures
select the output, and run it.
This assumes you're doing a very infrequent task. If you have to do this regularly (daily, weekly...), please let us know why!
There is no way to do it from T-SQL, or Enterprise Manager, so i had to write something from client code. i won't post all the code here, but the trick is to:
1) Get a list of all stored procedures
SELECT ROUTINE_NAME AS StoredProcedureName
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_TYPE = 'PROCEDURE' --as opposed to a function
ORDER BY ROUTINE_NAME
2) Get the stored procedure create T-SQL:
select
c.text
from dbo.syscomments c
where c.id = object_id(N'StoredProcedureName')
order by c.number, c.colid
option(robust plan)
3) Run the create statement with NOEXEC on, so that the syntax is checked, but it doesn't actually try to create the stored procedure:
connection("SET NOEXEC ON", ExecuteNoRecords);
connection(StoredProcedureCreateSQL, ExecuteNoRecords);
connection("SET NOEXEC ON", ExecuteNoRecords);
Here is an amendment which deals with multiple schemas
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[RefreshAllViews] AS
-- This sp will refresh all views in the catalog.
-- It enumerates all views, and runs sp_refreshview for each of them
DECLARE abc CURSOR FOR
SELECT TABLE_SCHEMA+'.'+TABLE_NAME AS ViewName
FROM INFORMATION_SCHEMA.VIEWS
OPEN abc
DECLARE #ViewName varchar(128)
-- Build select string
DECLARE #SQLString nvarchar(2048)
FETCH NEXT FROM abc
INTO #ViewName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQLString = 'EXECUTE sp_RefreshView ['+#ViewName+']'
PRINT #SQLString
EXECUTE sp_ExecuteSQL #SQLString
FETCH NEXT FROM abc
INTO #ViewName
END
CLOSE abc
DEALLOCATE abc
GO