Modify Stored Procedure Post Deployment - sql

Is there a way to modify a stored procedure in post deployment scripts?
I am trying to modify stored procedures in a Visual Studio 2013 SQL Server Database Project and SQL Server Express 2012. I know that I can manually modify the stored procedure in the build, but depending on what branch of our application I'm working on, I need the stored procedures to change.
I've tried a number of ways to write scripts but always wind up with SQL80001 or SQL72007 around the syntax ALTER PROCEDURE or CREATE PROCEDURE. When I attempt to recreate the procedure, I do Drop it first.
The following script is being linked to Script.PostDeployment.sql.
ALTER PROCEDURE [dbo].[spCreateTemplate]
(
#name varchar(250),
#dataSourceID nvarchar(1)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #id uniqueidentifier
SELECT #id = NEWID()
INSERT TemplateInfo
(
ID,
Name,
DataModeID,
DataSourceID,
StartDepth,
EndDepth,
StartDateTime,
EndDateTime,
Increment,
IsActive,
IsRealTime,
IsLogarithmic,
CreatedBy,
CreatedUTCDate,
ModifiedBy,
ModifiedUTCDate
)
VALUES
(
#id,
#name,
1,
#dataSourceID,
NULL,
NULL,
NULL,
NULL,
1,
0,
1,
0,
SUSER_SNAME(),
GETUTCDATE(),
SUSER_SNAME(),
GETUTCDATE()
)
SELECT #id
END

I have finally been able to resolve this issue. In order to alter the procedure, I had to run the stored procedure sp_executesql and pass it the script to create the stored procedure. Here is an example of what I did:
GO
SET QUOTED_IDENTIFIER ON
GO
DECLARE #sqlCmd nvarchar (4000)
IF EXISTS(select * FROM sys.procedures where name = <spName>
begin
drop procedure <spName>
SELECT #sqlCmd = 'CREATE PROCEDURE [dbo].[<spName>]
(<#variables datatype>)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #id uniqueidentifier
SELECT #id = NEWID()
INSERT
<tablename>(<columns>)
VALUES
(<values>)
SELECT #ID
END'
EXEC sp_executesql #sqlCmd
END
else
begin
SELECT #sqlCmd = 'CREATE PROCEDURE [dbo].[<spName>]
(<#variables datatype>)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #id uniqueidentifier
SELECT #id = NEWID()
INSERT
<tablename>(<columns>)
VALUES
(<values>)
SELECT #ID
END'
EXEC sp_executesql #sqlCmd
END

I'm also having this issue in post-deployment.
scenario:
2 files
post-deployment code
:r "file1.sql"
:r "file2.sql"
what I did to fix this is to ADD the following in every files (e.g file1.sql and fil12.sql)
...<afterENDline>
<line>
<line>
GO
<line>
I think the reason why this occur is because the post-deployment copies all the lines like :r "files1.sql" into a single .sql file.
therefore, I think you can also fix it by doing this way:
:r "file1.sql" GO
:r "file2.sql" GO
Hope this helps.

Related

Assigining value to parameter using variable in stored procedure in SQL Server

I have a simple stored procedure with one parameter #Name which I want to replace with another variable.
I am actually looking for SQL injection character and if name contains -- then it should replace it with blank. The stored procedure shown below, it is executing without an error, but not replacing the string for example let is say user searches for EXEC John'''select * FROM TEST2 -- which has SQL injection statement in it
CREATE PROCEDURE GetStudentDetails
#Name nvarchar(300)
AS
BEGIN
SET NOCOUNT ON;
SELECT #Name = REPLACE(#Name ,'--','');
SET #Name = REPLACE(#Name ,'--','');
SELECT *
FROM TABLENAME
WHERE Name LIKE N'%'+ #Name +'%'
END
Updated stored procedure:
CREATE PROCEDURE GetStudentDetails
#Name nvarchar(300)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SafeSearchItem nvarchar(30);
SELECT #SafeSearchItem = REPLACE(#Name ,N'--',N'')
SET #SafeSearchItem = REPLACE(#Name ,N'--',N'')
SELECT *
FROM TABLENAME
WHERE Name LIKE N'%'+ #SafeSearchItem +'%'
END
EXEC
EXEC John'''select * FROM TEST2 --
In the second stored procedure, I am always able to inject SQL - not sure it is my system?
As it stands, we can't answer the question, as, well there isn't a question applicable for information we're been provided. There is no risk of injection in the SP we have, thus, there is not answer on how to avoid it.
Anyway, instead, what i'm going to do is show firstly why that SP isn't subject to injection and then change it so it would be, and how the limited "fix" in it could easily be avoided.
Firstly, let's start with a simple table and data (I strongly suggest running any following scripts in a Sandbox environment!):
USE Sandbox;
GO
CREATE TABLE InjectionReady (ID int IDENTITY(1,1), SomeText varchar(500));
INSERT INTO InjectionReady
VALUES ('Here is some text'),
('Life is like a box a chocolates'),
('Milk Chocolate is my favourite'),
('Cheese is dairy product'),
('Chocolate is a dairy product'),
('Cows say "moo"!'),
('English Cat says "Meow"'),
('Japanese Cat says "Nyaa"');
GO
OK, and now let's create your SP (amended for our object). and then do some tests:
CREATE PROCEDURE NonInjectionSearch #Wildcard nvarchar(100) AS
SELECT #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SET #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N'%'+ #Wildcard +N'%';
GO
EXEC NonInjectionSearch 'Chocolate';
EXEC NonInjectionSearch '''; DROP TABLE InjectionReady;--';
EXEC NonInjectionSearch '''; DROP TABLE InjectionReady; SELECT ''';
No injection. Great! Ok, now for an SP that could suffer injection:
CREATE PROCEDURE InjectionSearch #Wildcard nvarchar(100) AS
SELECT #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SET #Wildcard = REPLACE(#Wildcard ,N'--',N'');
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N''%'+ #Wildcard + N'%'';'; --Yes, intentional non parametrisation
PRINT #SQL;
EXEC (#SQL);
GO
EXEC InjectionSearch 'Chocolate';
GO
EXEC InjectionSearch '''; CREATE TABLE Injection1(ID int);--'; --This'll fail
GO
EXEC InjectionSearch '''; CREATE TABLE Injection2(ID int); SELECT '''; --Oh! This worked!
GO
So, how could you avoid this? Well, Parametrise your dynamic SQL:
CREATE PROCEDURE ParamSearch #Wildcard nvarchar(100) AS
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N''%'' + #pWildCard +''%'';'; --Yes, intentional non parametrisation
PRINT #SQL;
EXEC sp_executesql #SQL, N'#pWildcard nvarchar(500)', #pWildCard = #Wildcard;
GO
EXEC ParamSearch 'Chocolate';
GO
EXEC ParamSearch '''; CREATE TABLE Injection1(ID int);--'; --Won't inject
GO
EXEC ParamSearch '''; CREATE TABLE Injection2(ID int); SELECT '''; --Oh! this didn't inject either
Dynamic objects bring another level to this, however, I'll only cover this if required; as it stands (like I said at the start) the question asked can't happen for the scenario we have.
Clean up:
DROP TABLE Injection2;
DROP PROC ParamSearch;
DROP PROC InjectionSearch;
DROP PROC NonInjectionSearch;
DROP TABLE InjectionReady;

Unable to pull out resetSet for insert within stored procedure

I have been looking online for a few days to find a solution and
I may be asking the wrong questions.
I have the following stored proc which on insertion of a row to a db I want to get back the output int (#outResult). This is the stored proc :
USE [DB1]
GO
/****** Object: StoredProcedure [dbo].[storedProc1] Script Date: 04/12/2016 10:16:23 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[storedProc1]
-- Add the parameters for the stored procedure here
#inParam nvarchar(max),
#outResult int = 0 OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF NOT EXISTS
(
SELECT ID
FROM dbo.table1
WHERE Field1 = #inParam
)
Insert into [DB1].[dbo].[table1]
(
Field1
)
Values (
#inParam
)
SET #outResult = SCOPE_IDENTITY()
END
When I run the following SQL and try to retrieve the resultSet :
SELECT * FROM (EXEC storedProc1 'field1')
I get the following error:
"Incorrect syntax near the keyword 'exec'"
Maybe I am approaching this problem wrong from the first place?
I will continue to look into this and provide a solution if I find one.Any ideas?
To get the value of the output parameter you need to supply the call to the SP with an output argument. You do that by specifying the OUTPUT option on the second parameter.
DECLARE #returned_ID INT;
EXEC storedProc1 #inParam = 'field1',
#outResult = #returned_ID OUTPUT
SELECT #returned_ID
If you want to retrieve just the out parameter, then You can read it as :
DECLARE #output int
EXEC storedProc1 'field1',#output OUTPUT
SELECT #output

Trigger Failing when calling Stored Procedure

I am truly hoping someone can help me out...
I have a trigger to handle the insert of a new record to a table. This trigger, as you will see below, inserts a record into another table, which in turns executes a trigger on that table, that calls a stored procedure (I tried to do it within the trigger itself, but it failed and was difficult to test where it was failing, so I moved it into its own little unit.)
Within the stored procedure, there is a call to extract information from the Active Directory database (ADSI) and update the newly inserted record. However, this is where it fails when called by the trigger. When I call it by simply executing it, and passing along the record to be updated, it works great... Can anyone point me in the right direction? Please!!!
Trigger #1 in YYY
USE [YYY]
GO
/****** Object: Trigger [dbo].[NewCustodian] Script Date: 08/04/2014 09:38:11 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[NewCustodian]
ON [YYY].[dbo].[Custodians]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
DECLARE #CaseID varchar(20);
DECLARE DBcursor CURSOR FOR
SELECT [XXX].[dbo].[tblCase].CaseID from [XXX].[dbo].[tblCase] Where [XXX].[dbo].[tblCase].SQLSVR_Case_ID = 'YYY';
Open DBcursor; FETCH DBCursor into #CaseID;
CLOSE DBcursor; DEALLOCATE DBcursor;
DECLARE #NAME varchar(255);
DECLARE #TAG varchar(255);
SELECT #NAME = name FROM inserted;
SELECT #TAG = tag FROM inserted;
IF NOT EXISTS (Select eID from [XXX].[dbo].[tblNames]
WHERE eID = #TAG and CaseID = #CaseID)
BEGIN
INSERT INTO [XXX].[dbo].[tblNames] (CaseID, Name, eID)
Values (#CaseID, #NAME, #Tag);
END
END
Trigger #2 in XXX
USE [XXX]
GO
/****** Object: Trigger [dbo].[tblNames_New] Script Date: 08/04/2014 08:56:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:
-- Create date:
-- Description:
-- =============================================
ALTER TRIGGER [dbo].[tblNames_New]
ON [XXX].[dbo].[tblNames]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
DECLARE #NamesID varchar(10)
DECLARE #TAG varchar(10);
DECLARE #return_value int
SELECT #NamesID = namesID FROM inserted
EXEC dbo.UpdateNames #NamesID;
End
Stored procedure:
USE [XXX]
GO
/****** Object: StoredProcedure [dbo].[UpdateNames] Script Date: 08/04/2014 08:14:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:
-- Create date:
-- Description:
-- =============================================
ALTER PROCEDURE [dbo].[UpdateNames]
#NamesID int
AS
BEGIN
SET FMTONLY OFF;
SET NOCOUNT ON;
DECLARE #eID varchar(10);
DECLARE #TAG varchar(10);
DECLARE #SQL nvarchar(555);
DECLARE #DBresults as table (
eID nvarchar(100),
mobile nvarchar(100),
mail nvarchar(100),
phone nvarchar(100),
name nvarchar(50),
legacyExchangeDN nvarchar(100),
Title nvarchar(100),
homeDirectory nvarchar(100));
DECLARE #mobile nvarchar(100)
DECLARE #mail nvarchar(100)
DECLARE #phone nvarchar(100) = 'Error'
DECLARE #name nvarchar(100)
DECLARE #legacyExchangeDN nvarchar(100)
DECLARE #Title nvarchar(100) = 'Error'
DECLARE #homeDirectory nvarchar(100)
SET #eID = (Select eID from [XXX].[dbo].[tblNames] Where NamesID = #NamesID)
SET #SQL = N'SELECT * FROM OpenQuery ( ADSI, ''SELECT homeDirectory,Title,legacyExchangeDN,displayName, telephoneNumber, mail, mobile,samAccountName
FROM ''''LDAP://domain.com''''
WHERE objectClass = ''''User'''' and samAccountName = ''''' + #eID+ ''''''') As tblADSI'
INSERT INTO #DBresults
EXEC sp_executesql #SQL
DECLARE DBcursor CURSOR FOR
SELECT * from #DBresults;
Open DBcursor; FETCH DBCursor into #eID, #mobile, #mail, #phone, #Name, #legacyExchangeDN, #Title, #homeDirectory;
CLOSE DBcursor; DEALLOCATE DBcursor;
UPDATE XXX.dbo.tblNames
SET Job_Title = #Title,
Phone = #Phone
Where NamesID = #NamesID;
END
As I said in my comment - a trigger should be extremely small, nimble, lean - do not do any extensive and time-consuming processing inside a trigger, and avoid anything that would cause performance bottlenecks, especially cursors!
The reason for this is the fact that a trigger will be triggered whenever an INSERT operation happens, you have no control over when and how many times it gets called. The main app will wait and hang while the trigger is at work - so therefore, don't make this a long time - return very quickly from your trigger to go on with your main app.
My approach would be:
create a new separate table where you insert some key pieces of information into from your first original trigger
CREATE TABLE NewCustodianInserted
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
CaseID VARCHAR(20),
Tag VARCHAR(255),
Handled BIT DEFAULT (0)
);
change your original trigger on the Custodians table to just insert those key pieces of information into your new "command" table:
CREATE TRIGGER [dbo].[NewCustodian]
ON [YYY].[dbo].[Custodians]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
-- insert key pieces about the new custodian into "command" table
INSERT INTO dbo.NewCustodianInserted (CaseID, Tag)
SELECT i.CaseId, i.Tag
FROM Inserted i
WHERE NOT EXISTS (SELECT * FROM [XXX].[dbo].[tblNames] WHERE eID = i.Tag AND CaseID = i.CaseID)
END
in a separate process, e.g. a SQL Server Agent job that is scheduled to run every 5 mînutes (or whatever makes sense for your application), read the "command" table, get the new custodians to handle, call that long-running stored procedure updating Active Directory from it. Here, since this runs asynchronously from your main application, it's ok to use a cursor which you almost have to since you want to call a stored procedure for every row in your new table.
CREATE PROCEDURE HandleNewCustodians
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CaseID VARCHAR(20);
DECLARE #Tag VARCHAR(255);
DECLARE #NamesID varchar(10);
DECLARE CustodianCursor CURSOR FAST_FORWARD
FOR
SELECT CaseID, Tag FROM dbo.NewCustodianInserted WHERE Handled = 0
OPEN CustodianCursor
FETCH NEXT FROM CustodianCursor INTO #CaseID, #Tag;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #NamesID = NameID
FROM [XXX].[dbo].[tblNames] WHERE eID = #Tag AND CaseID = #CaseID
EXEC dbo.UpdateNames #NamesID;
FETCH NEXT FROM CustodianCursor INTO #CaseID, #Tag;
END
CLOSE CustodianCursor;
DEALLOCATE CustodianCursor;
END

How to SELECT FROM stored procedure

I have a stored procedure that returns rows:
CREATE PROCEDURE MyProc
AS
BEGIN
SELECT * FROM MyTable
END
My actual procedure is a little more complicated, which is why a stored procedure is necessary.
Is it possible to select the output by calling this procedure?
Something like:
SELECT * FROM (EXEC MyProc) AS TEMP
I need to use SELECT TOP X, ROW_NUMBER, and an additional WHERE clause to page my data, and I don't really want to pass these values as parameters.
You can
create a table variable to hold the
result set from the stored proc and
then
insert the output of the
stored proc into the table variable,
and then
use the table variable
exactly as you would any other
table...
... sql ....
Declare #T Table ([column definitions here])
Insert #T Exec storedProcname params
Select * from #T Where ...
You can use a User-defined function or a view instead of a procedure.
A procedure can return multiple result sets, each with its own schema. It's not suitable for using in a SELECT statement.
You either want a Table-Valued function or insert your EXEC into a temporary table:
INSERT INTO #tab EXEC MyProc
You need to declare a table type which contains the same number of columns your store procedure is returning. Data types of the columns in the table type and the columns returned by the procedures should be same
declare #MyTableType as table
(
FIRSTCOLUMN int
,.....
)
Then you need to insert the result of your stored procedure in your table type you just defined
Insert into #MyTableType
EXEC [dbo].[MyStoredProcedure]
In the end just select from your table type
Select * from #MyTableType
You must read about OPENROWSET and OPENQUERY
SELECT *
INTO #tmp FROM
OPENQUERY(YOURSERVERNAME, 'EXEC MyProc #parameters')
It is not necessary use a temporary table.
This is my solution
SELECT * FROM
OPENQUERY(YOURSERVERNAME, 'EXEC MyProc #parameters')
WHERE somefield = anyvalue
You can copy output from sp to temporaty table.
CREATE TABLE #GetVersionValues
(
[Index] int,
[Name] sysname,
Internal_value int,
Character_Value sysname
)
INSERT #GetVersionValues EXEC master.dbo.xp_msver 'WindowsVersion'
SELECT * FROM #GetVersionValues
drop TABLE #GetVersionValues
Try converting your procedure in to an Inline Function which returns a table as follows:
CREATE FUNCTION MyProc()
RETURNS TABLE AS
RETURN (SELECT * FROM MyTable)
And then you can call it as
SELECT * FROM MyProc()
You also have the option of passing parameters to the function as follows:
CREATE FUNCTION FuncName (#para1 para1_type, #para2 para2_type , ... )
And call it
SELECT * FROM FuncName ( #para1 , #para2 )
You can cheat a little with OPENROWSET :
SELECT ...fieldlist...
FROM OPENROWSET('SQLNCLI', 'connection string', 'name of sp')
WHERE ...
This would still run the entire SP every time, of course.
If 'DATA ACCESS' false,
EXEC sp_serveroption 'SQLSERVERNAME', 'DATA ACCESS', TRUE
after,
SELECT * FROM OPENQUERY(SQLSERVERNAME, 'EXEC DBNAME..MyProc #parameters')
it works.
Use OPENQUERY, and before execute set SET FMTONLY OFF; SET NOCOUNT ON;
Try this sample code:
SELECT top(1)*
FROM
OPENQUERY( [Server], 'SET FMTONLY OFF; SET NOCOUNT ON; EXECUTE [database].[dbo].[storedprocedure] value,value ')
If you get the error 'Server is not configured for DATA ACCESS',
use this:
EXEC sp_serveroption 'YourServer', 'DATA ACCESS', TRUE
For the sake of simplicity and to make it re-runnable, I have used a system StoredProcedure "sp_readerrorlog" to get data:
-----USING Table Variable
DECLARE #tblVar TABLE (
LogDate DATETIME,
ProcessInfo NVARCHAR(MAX),
[Text] NVARCHAR(MAX)
)
INSERT INTO #tblVar Exec sp_readerrorlog
SELECT LogDate as DateOccured, ProcessInfo as pInfo, [Text] as Message FROM #tblVar
-----(OR): Using Temp Table
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp;
CREATE TABLE #temp (
LogDate DATETIME,
ProcessInfo NVARCHAR(55),
Text NVARCHAR(MAX)
)
INSERT INTO #temp EXEC sp_readerrorlog
SELECT * FROM #temp
It sounds like you might just need to use a view. A view allows a query to be represented as a table so it, the view, can be queried.
If your server is called SERVERX for example, this is how I did it...
EXEC sp_serveroption 'SERVERX', 'DATA ACCESS', TRUE;
DECLARE #CMD VARCHAR(1000);
DECLARE #StudentID CHAR(10);
SET #StudentID = 'STUDENT01';
SET #CMD = 'SELECT * FROM OPENQUERY([SERVERX], ''SET FMTONLY OFF; SET NOCOUNT ON; EXECUTE MYDATABASE.dbo.MYSTOREDPROC ' + #StudentID + ''') WHERE SOMEFIELD = SOMEVALUE';
EXEC (#CMD);
To check this worked, I commented out the EXEC() command line and replaced it with SELECT #CMD to review the command before trying to execute it! That was to make sure all the correct number of single-quotes were in the right place. :-)
I hope that helps someone.

Is there a way to persist a variable across a go?

Is there a way to persist a variable across a go?
Declare #bob as varchar(50);
Set #bob = 'SweetDB';
GO
USE #bob --- see note below
GO
INSERT INTO #bob.[dbo].[ProjectVersion] ([DB_Name], [Script]) VALUES (#bob,'1.2')
See this SO question for the 'USE #bob' line.
Use a temporary table:
CREATE TABLE #variables
(
VarName VARCHAR(20) PRIMARY KEY,
Value VARCHAR(255)
)
GO
Insert into #variables Select 'Bob', 'SweetDB'
GO
Select Value From #variables Where VarName = 'Bob'
GO
DROP TABLE #variables
go
The go command is used to split code into separate batches. If that is exactly what you want to do, then you should use it, but it means that the batches are actually separate, and you can't share variables between them.
In your case the solution is simple; you can just remove the go statements, they are not needed in that code.
Side note: You can't use a variable in a use statement, it has to be the name of a database.
I prefer the this answer from this question
Global Variables with GO
Which has the added benefit of being able to do what you originally wanted to do as well.
The caveat is that you need to turn on SQLCMD mode (under Query->SQLCMD) or turn it on by default for all query windows (Tools->Options then Query Results->By Default, open new queries in SQLCMD mode)
Then you can use the following type of code (completely ripped off from that same answer by Oscar E. Fraxedas Tormo)
--Declare the variable
:setvar MYDATABASE master
--Use the variable
USE $(MYDATABASE);
SELECT * FROM [dbo].[refresh_indexes]
GO
--Use again after a GO
SELECT * from $(MYDATABASE).[dbo].[refresh_indexes];
GO
If you are using SQL Server you can setup global variables for entire scripts like:
:setvar sourceDB "lalalallalal"
and use later in script as:
$(sourceDB)
Make sure SQLCMD mode is on in Server Managment Studi, you can do that via top menu Click Query and toggle SQLCMD Mode on.
More on topic can be found here:
MS Documentation
Temp tables are retained over GO statements, so...
SELECT 'value1' as variable1, 'mydatabasename' as DbName INTO #TMP
-- get a variable from the temp table
DECLARE #dbName VARCHAR(10) = (select top 1 #TMP.DbName from #TMP)
EXEC ('USE ' + #dbName)
GO
-- get another variable from the temp table
DECLARE #value1 VARCHAR(10) = (select top 1 #TMP.variable1 from #TMP)
DROP TABLE #TMP
It's not pretty, but it works
Create your own stored procedures which save/load to a temporary table.
MyVariableSave -- Saves variable to temporary table.
MyVariableLoad -- Loads variable from temporary table.
Then you can use this:
print('Test stored procedures for load/save of variables across GO statements:')
declare #MyVariable int = 42
exec dbo.MyVariableSave #Name = 'test', #Value=#MyVariable
print(' - Set #MyVariable = ' + CAST(#MyVariable AS VARCHAR(100)))
print(' - GO statement resets all variables')
GO -- This resets all variables including #MyVariable
declare #MyVariable int
exec dbo.MyVariableLoad 'test', #MyVariable output
print(' - Get #MyVariable = ' + CAST(#MyVariable AS VARCHAR(100)))
Output:
Test stored procedures for load/save of variables across GO statements:
- Set #MyVariable = 42
- GO statement resets all variables
- Get #MyVariable = 42
You can also use these:
exec dbo.MyVariableList -- Lists all variables in the temporary table.
exec dbo.MyVariableDeleteAll -- Deletes all variables in the temporary table.
Output of exec dbo.MyVariableList:
Name Value
test 42
It turns out that being able to list all of the variables in a table is actually quite useful. So even if you do not load a variable later, its great for debugging purposes to see everything in one place.
This uses a temporary table with a ## prefix, so it's just enough to survive a GO statement. It is intended to be used within a single script.
And the stored procedures:
-- Stored procedure to save a variable to a temp table.
CREATE OR ALTER PROCEDURE MyVariableSave
#Name varchar(255),
#Value varchar(MAX)
WITH EXECUTE AS CALLER
AS
BEGIN
SET NOCOUNT ON
IF NOT EXISTS (select TOP 1 * from tempdb.sys.objects where name = '##VariableLoadSave')
BEGIN
DROP TABLE IF EXISTS ##VariableLoadSave
CREATE TABLE ##VariableLoadSave
(
Name varchar(255),
Value varchar(MAX)
)
END
UPDATE ##VariableLoadSave SET Value=#Value WHERE Name=#Name
IF ##ROWCOUNT = 0
INSERT INTO ##VariableLoadSave SELECT #Name, #Value
END
GO
-- Stored procedure to load a variable from a temp table.
CREATE OR ALTER PROCEDURE MyVariableLoad
#Name varchar(255),
#Value varchar(MAX) OUT
WITH EXECUTE AS CALLER
AS
BEGIN
IF EXISTS (select TOP 1 * from tempdb.sys.objects where name = '##VariableLoadSave')
BEGIN
IF NOT EXISTS(SELECT TOP 1 * FROM ##VariableLoadSave WHERE Name=#Name)
BEGIN
declare #ErrorMessage1 as varchar(200) = 'Error: cannot find saved variable to load: ' + #Name
raiserror(#ErrorMessage1, 20, -1) with log
END
SELECT #Value=CAST(Value AS varchar(MAX)) FROM ##VariableLoadSave
WHERE Name=#Name
END
ELSE
BEGIN
declare #ErrorMessage2 as varchar(200) = 'Error: cannot find saved variable to load: ' + #Name
raiserror(#ErrorMessage2, 20, -1) with log
END
END
GO
-- Stored procedure to list all saved variables.
CREATE OR ALTER PROCEDURE MyVariableList
WITH EXECUTE AS CALLER
AS
BEGIN
IF EXISTS (select TOP 1 * from tempdb.sys.objects where name = '##VariableLoadSave')
BEGIN
SELECT * FROM ##VariableLoadSave
ORDER BY Name
END
END
GO
-- Stored procedure to delete all saved variables.
CREATE OR ALTER PROCEDURE MyVariableDeleteAll
WITH EXECUTE AS CALLER
AS
BEGIN
DROP TABLE IF EXISTS ##VariableLoadSave
CREATE TABLE ##VariableLoadSave
(
Name varchar(255),
Value varchar(MAX)
)
END
If you just need a binary yes/no (like if a column exists) then you can use SET NOEXEC ON to disable execution of statements. SET NOEXEC ON works across GO (across batches). But remember to turn EXEC back on with SET NOEXEC OFF at the end of the script.
IF COL_LENGTH('StuffTable', 'EnableGA') IS NOT NULL
SET NOEXEC ON -- script will not do anything when column already exists
ALTER TABLE dbo.StuffTable ADD EnableGA BIT NOT NULL CONSTRAINT DF_StuffTable_EnableGA DEFAULT(0)
ALTER TABLE dbo.StuffTable SET (LOCK_ESCALATION = TABLE)
GO
UPDATE dbo.StuffTable SET EnableGA = 1 WHERE StuffUrl IS NOT NULL
GO
SET NOEXEC OFF
This compiles statements but does not execute them. So you'll still get "compile errors" if you reference schema that doesn't exist. So it works to "turn off" the script 2nd run (what I'm doing), but does not work to turn off parts of the script on 1st run, because you'll still get compile errors if referencing columns or tables that don't exist yet.
You can make use of NOEXEC follow he steps below:
Create table
#temp_procedure_version(procedure_version varchar(5),pointer varchar(20))
insert procedure versions and pointer to the version into a temp table #temp_procedure_version
--example procedure_version pointer
insert into temp_procedure_version values(1.0,'first version')
insert into temp_procedure_version values(2.0,'final version')
then retrieve the procedure version, you can use where condition as in the following statement
Select #ProcedureVersion=ProcedureVersion from #temp_procedure_version where
pointer='first version'
IF (#ProcedureVersion='1.0')
BEGIN
SET NOEXEC OFF --code execution on
END
ELSE
BEGIN
SET NOEXEC ON --code execution off
END
--insert procedure version 1.0 here
Create procedure version 1.0 as.....
SET NOEXEC OFF -- execution is ON
Select #ProcedureVersion=ProcedureVersion from #temp_procedure_version where
pointer='final version'
IF (#ProcedureVersion='2.0')
BEGIN
SET NOEXEC OFF --code execution on
END
ELSE
BEGIN
SET NOEXEC ON --code execution off
END
Create procedure version 2.0 as.....
SET NOEXEC OFF -- execution is ON
--drop the temp table
Drop table #temp_procedure_version