Syntax check all stored procedures? - sql-server

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

Related

Could not find stored procedure 'sp_msforeachtable' while looping through server for stats (SSMS)

I've written this to loop through each database on a server, collecting the statistics for each table and storing them in a temp table. Eventually, I'll integrate this into a more permanent structure, but for now I'm just trying to get this working. My problem is, after 57 databases, I get the error stating it can't find the stored procedure sp_msforeachtable.
I've verified that this stored procedure exists on every database on the server and on the server level.
I've excluded this database in the findings by adding it to the "where name not in" condition, and it just moves to the next one in the list and gives the same error.(I've confirmed it exists on the next database also). I've actually done this for the next 6 databases.
This is causing me to not collect accurate information. Am I running out of resources somewhere?
DECLARE #Database TABLE (DbName SYSNAME);
IF OBJECT_ID('tempdb.dbo.#TableLvlSizes', 'U') IS NOT NULL
BEGIN
PRINT 'dropping table'
DROP TABLE tempdb.dbo.#TableLvlSizes;
END
CREATE TABLE #TableLvlSizes (
TableName nvarchar(128)
,NumberOfRows varchar(50)
,ReservedSpace varchar(50)
,TableDataSpace varchar(50)
,IndexSize varchar(50)
,unused varchar(50))
DECLARE #DbName AS SYSNAME;
DECLARE #Sql1 AS VARCHAR(MAX);
SET #DbName = '';
INSERT INTO #Database (DbName)
SELECT NAME
FROM sys.databases
where name not in ('tempdb')
ORDER BY NAME ASC;
WHILE #DbName IS NOT NULL
BEGIN
SET #DbName = (
SELECT MIN(DbName)
FROM #Database
WHERE DbName > #DbName
);
print #DbName;
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_msforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]''
'
Exec (#SQL1);
END
If someone is using Azure SQL, they will not find sp_MSforeachtable since it is not available in Azure SQL.
You may need to create one for yourself.
Since you already verified that the stored procedure does in fact exist, I believe your database is case sensitive. Therefore, the error is still accurate. Basically, the stored procedure with the case you used does not exist. The actual procedure name is sp_MSforeachtable
In your code, you are using the following:
Exec sp_msforeachtable
If you change your code to use the proper case for the stored procedure to be sp_MSforeachtable, it should work:
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_MSforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]'''

Using EXEC() to re-create stored procedure doesn't find errors

I'm checking the validity of existing stored procedures, by obtaining their definition and running the ALTER statement on them.
The problem I have is that any stored procedure which doesn't compile (because a dependency has gone) isn't being flagged as such.
If I try to run the same ALTER command in SSMS I do get the error message.
EDIT: No, I don't....
DECLARE #def nvarchar(MAX)
BEGIN TRY
-- refresh the stored procedure
SELECT #def = REPLACE(definition,'CREATE PROCEDURE ','ALTER PROCEDURE ')
FROM sys.sql_modules
WHERE ... -- selecting/limiting clause
EXEC (#def);
END TRY
BEGIN CATCH
PRINT 'Validation failed : ' + ERROR_MESSAGE()
END CATCH
What do I have to do to trap the non-compile error? Thanks
SQL Server stored procedures use deferred name resolution:
When a stored procedure is created, the statements in the procedure are parsed for syntactical accuracy. If a syntactical error is encountered in the procedure definition, an error is returned and the stored procedure is not created. If the statements are syntactically correct, the text of the stored procedure is stored in the sys.sql_modules catalog view.
When a stored procedure is executed for the first time, the query processor reads the text of the stored procedure from the sys.sql_modules catalog view and checks that the names of the objects used by the procedure are present. This process is called deferred name resolution because table objects referenced by the stored procedure need not exist when the stored procedure is created, but only when it is executed.
So the behavior you observe is intentional. What you need is to find out what procedures depend on your missing tables. For this, see View the Dependencies of a Stored Procedure and the proper answer depends on your SQL Server version. SQL Server 2016 is somehow better at tracking this information and offers better views. Before that the process was notoriously difficult unreliable, read Keeping sysdepends up to date in SQL Server 2008.
Forget this - barking up the wrong tree ;-((
The stored proc will compile OK even if its dependencies have gone.
The editor in SSMS highlights the missing items, but doesn't stop the ALTER statement from working.
This query will identify all stored procs with missing dependencies:
-- table variable to store procedure names
DECLARE #v TABLE (RecID INT IDENTITY(1,1), spname sysname)
-- retrieve the list of stored procedures
INSERT INTO #v(spname)
SELECT
'[' + s.[name] + '].[' + sp.name + ']'
FROM sys.procedures sp
INNER JOIN sys.schemas s ON s.schema_id = sp.schema_id
WHERE is_ms_shipped = 0
AND sp.name like 'Get%'
-- counter variables
DECLARE #cnt INT, #Tot INT
SELECT #cnt = 1
SELECT #Tot = COUNT(*) FROM #v
DECLARE #spname sysname
DECLARE #ref nvarchar(MAX)
-- start the loop
WHILE #Cnt <= #Tot BEGIN
SELECT #spname = spname
FROM #v
WHERE RecID = #Cnt
BEGIN
SELECT #ref = referenced_entity_name
FROM sys.dm_sql_referenced_entities (#spname, 'OBJECT')
WHERE referenced_id IS NULL;
END
SET #Cnt = #cnt + 1
END

Copy a view definition from one database to another one in SQL Server

I am developping a script that allows me to create all the views present in database1 in another database (database2).
I am trying to do that using a cursor that loop on all the views of database1 and then try to execute the definition of that view on the second database.
Unfortunately it doesn't work. I get the following error:
Syntaxe incorrecte vers 'go'.
Msg 111, Niveau 15, État 1, Ligne 14
'CREATE VIEW' doit être la première instruction d'un traitement de requêtes.
This is my code
declare #database2 varchar(50), #database1 varchar(50)
set #database2 = 'Local'
set #database1 = 'prod'
declare #Query nvarchar(max), #view_definition nvarchar(max), #count int
set #count = 0
declare curseur cursor for SELECT top 1 view_definition FROM prod.information_schema.VIEWS
open curseur
fetch curseur into #view_definition
While ##FETCH_STATUS = 0
Begin
set #count = #count + 1
--Print 'Vue N° ' + cast(#count as varchar) + ':'
set #Query = N'Use ' + #database2 +CHAR(13)+CHAR(10)+ 'go' + #view_definition +CHAR(13)+CHAR(10)+ 'go'
print #Query
exec sp_executesql #Query
fetch curseur into #view_definition
End
Close curseur
deallocate curseur
This code was executed from database1.
However when I execute the result of the 'print #Query' instruction, it works!!
Does anyone can help me to resolve the problem?
Thanks in advance
There are two things here:
You can't USE another database in a stored procedure, although is the other database is on the same server you can use CREATE VIEW databasename.schemaname.viewname (.... If it's on another server, yiu could try setting it up as a linked server and using servername.database.schema.viewname.
sp_executesql expects one statement, and doesn't accept GO to my
knowledge (which is what the error is telling you). You could try to
put in ';' instead (and you don't need the CHAR(13)'s if you do
although they make it easier to read).
In case this helps someone in the future, here's my solution to this problem. The full history of how I came up with it is over at Stored Procedure to Copy Views from Current Database to Another
CREATE PROCEDURE [dbo].[usp_Copy_View_To_Database]
#ViewName SYSNAME, -- The name of the view to copy over
#DatabaseName SYSNAME, -- The name of the database to copy the view to
#overwrite bit = 1 -- Whether to overwrite any existing view
AS
IF DB_ID(#DatabaseName) IS NULL -- Validate the database name exists
BEGIN
RAISERROR('Invalid Destination Database Name passed',16,1)
RETURN
END
SET NOCOUNT ON
IF #overwrite = 1 -- If set to overwrite, try to drop the remote view
BEGIN
DECLARE #DropViewStatement NVARCHAR(MAX) =
'EXEC ' + QUOTENAME(#DatabaseName) + '.sys.sp_executesql N''DROP VIEW IF EXISTS ' + QUOTENAME(#ViewName) + ';'';'
EXEC (#DropViewStatement);
END
-- Extract the saved definition of the view
DECLARE #ViewDefinition NVARCHAR(MAX);
SELECT #ViewDefinition = definition FROM sys.sql_modules WHERE [object_id] = OBJECT_ID(#ViewName);
-- Check for a mismatch between the internal view name and the expected name (TODO: Resolve this automatically?)
IF #ViewDefinition NOT LIKE ('%' + #ViewName + '%')
BEGIN
DECLARE #InternalName NVARCHAR(MAX) = SUBSTRING(#ViewDefinition, 3, CHARINDEX(char(10), #ViewDefinition, 3)-4);
PRINT ('Warning: The view named '+#ViewName+' has an internal definition name that is different ('+#InternalName+'). This may have been caused by renaming the view after it was created. You will have to drop and recreate it with the correct name.')
END
-- Substitute any hard-coded references to the current database with the destination database
SET #ViewDefinition = REPLACE(#ViewDefinition, db_name(), #DatabaseName);
-- Generate the dynamic SQL that will create the view on the remote database
DECLARE #CreateViewStatement NVARCHAR(MAX) =
'EXEC ' + QUOTENAME(#DatabaseName) + '.sys.sp_executesql N''' + REPLACE(#ViewDefinition,'''','''''') + ''';'
--PRINT '#CreateViewStatement: ' + #CreateViewStatement -- Can be used for debugging
-- Execute the create statement
EXEC (#CreateViewStatement);
In short, you need two layers of nested dynamic SQL:
Inner layer to execute the "CREATE VIEW" statement, which must be on its own. This is executed using EXEC SomeDatabase.sys.sp_executesql #CreateViewSQL
One more layer to dynamically specify "SomeDatabase" via a parameter (assuming you require the flexibility of copying it to a database not known at scripting time).
Calling the above stored proc in the inner-loop of the original poster's tentative solution should solve the problem of copying a view to another database.
Note that simply looping over all views might pose a challenge if views depend on one another. There may be some additional complexity involving resolving the dependency tree of views an copying them in the correct order. Alternatively, a "dumb and easy" approach might be to loop over all views, ignore failures, and keep repeating the process until all views have been created.

light queries that could help manage dependencies in the sp with dynamic sql

Is it natural that SQL Server does not catch objects dependencies in stored procedures through dynamic SQL:
CREATE PROCEDURE testSp (#filter nvarchar(max)) AS
exec ('select * from testTable where 1=1 AND '+ #filter)
Here SQL Server will not detect dependency between testTable and testSp.
What kind of "advice" do you have for the DBMS? I propose it could be very "cheap query" :
CREATE PROCEDURE testSp (#filter nvarchar(max)) AS
-- cheap query like 'select top 1 #id=id from testTable'
exec ('select * from testTable where 1=1 AND '+ #filter)
So the question is which queries could be good candidates for that purpose?
P.S. Of course I expect that they all will have their minuses..
When using dynamic SQL the query parts that are tekst (between quotes) are not detected as code by the IDE or the engine until the moment they are excuted. So this answers your first question, yes it is natural.
The only way around this that I can think of is to create a view using the generated output of the dynamic sql and check if the view definition is still valid at any point you want to check if the procedure is valid.
Usually when you need to do something like this there is an earlier departure from standard methods that if handled removes the need for such silly tricks.
Example:
USE demo
GO
DECLARE #sql NVARCHAR(MAX) = '
SELECT firstname, lastname FROM dbo.employees'
DECLARE #view NVARCHAR(MAX) = '
CREATE VIEW dbo.test_view
AS ' + #sql
EXEC sp_executesql #view
BEGIN TRY
DECLARE #validation int = (SELECT TOP 1 COUNT(*) FROM demo..test_view)
EXEC sp_executesql #sql
END TRY
BEGIN CATCH
PRINT 'Dynamic SQL out of date'
END CATCH
SET NOEXEC ON
select * from testTable
SET NOEXEC OFF
do the job: code really not executed, but dependecy is declared.

SQL Server / Create view from stored procedure

I am trying to create a view out of a stored procedure and am perplexed to see two opposing results from a very similar approach.
Example 1
CREATE PROCEDURE cv AS
GO
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
Whereas this example creates the view once the procedure is created for the 1st time, it will not recreate the view when I execute the procedure at a later stage using:
EXEC cv
Example 2
CREATE PROCEDURE cv
#table SYSNAME
AS
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW '+ #table +' AS SELECT * FROM someOtherTable'
This one instead does not create the view when the procedure is created for the first time but creates the view afterwards every time it is called by:
EXEC #sql;
Why is this the case? I think this is really confusing and does not make sense or does it?
For your 1st statement
CREATE PROCEDURE cv AS
GO --<-- This GO here terminates the batch
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
the GO batch terminator create the procedure and the EXECUTES the following statement straightaway. So it appears to you as you have created a procedure which created the view for you.
Infact these are two statements in two batches.
--BATCH 1
CREATE PROCEDURE cv AS
GO
--BATCH 2
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
Batch 1 Creates a procedure which has nothing inside it, but it creates a procedure object for you with no functionality/Definition at all.
Statement after the key word GO is executed separately and creates the view for you.
My Suggestion
Always check for an object's existence before you create it. I would write the procedure something like this..
CREATE PROCEDURE cv
AS
BEGIN
SET NOCOUNT ON;
IF OBJECT_ID ('test', 'V') IS NOT NULL
BEGIN
DROP VIEW test
END
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
END
In Example 1 - you are creating a view with a hard-coded name of test. On subsequent runs of your proc, as the view already exists, SQL Server will throw an error because you are trying to create a view with the same name as one that already exists.
In Example 2, you are passing in the name of the view as a parameter. This will always create a new view unless the #table value you pass in corresponds to an existing view.
Just wondering - why are you creating a view with a stored proc? This is not something you would normally do in SQL.
You need to change
EXEC #sql to EXEC (sql)
EXEC can be use to run stored procedure, not dynamic sql
eg.
declare #dd varchar(100)='Select ''A'''
exec (#dd) ->will work fine
exec #dd -> Error
Read more about The Curse and Blessings of Dynamic SQL
Create view statement cannot be used with a stored procedure
EXEC ('CREATE VIEW ViewName AS SELECT * FROM OPENQUERY(Yourservername,''EXECUTE [databasename].[dbo].[sp_name] ''''' +Parameter + ''''''')')

Resources