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

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

Related

Adding and updating columns a posteriori in temp tables in SQL Server

I'm trying to alter an existing temp table that was created and populated in the stored procedure calling this one where I'm doing these changes. I can't change the calling stored procedure and I need to add columns to the temp table, so I tried this:
ALTER TABLE #MyTemp
ADD Column1 VARCHAR(100);
UPDATE x
SET Column1 = a.SomeColumn
FROM #MyTemp x
INNER JOIN dbo.AnotherTable a (NOLOCK) ON a.ColumnName = x.ColumnName
WHERE somecondition;
It compiles, but when I run it I get:
Msg 207, Level 16, State 1, Procedure ProcName, Line #
Invalid column name 'Column1'
Looks like this code is not even being executed.
Could someone please tell me if this is possible and how?
Thanks.
You can apparently do this, but you can't insert into the altered temp table in the same scope you alter it. Don't ask me why.
You can use dynamic sql in the the second proc to get to a lower nesting level, or call another proc. Like this:
use tempdb
go
create or alter proc a
as
begin
create table #t(id int)
exec b
select * from #t
end
go
create or alter proc b
as
begin
alter table #t add a int
exec c
end
go
create or alter proc c
as
begin
insert into #t(id,a) values (1,1)
end
go
As David Browne mentioned on his answer above, this can also be achieved by using dynamic sql, but there is a catch, and here is how:
DECLARE #ColName1 NVARCHAR(100)
DECLARE #DynamicSQL NVARCHAR(500)
SET #ColName1='Column1'
SET #DynamicSQL = 'ALTER TABLE #MyTemp ADD ['+ CAST(#ColName1 AS NVARCHAR(100)) +']
NVARCHAR(100) NULL; '
EXEC(#DynamicSQL)
SET #DynamicSQL = 'UPDATE x SET '+ CAST(#ColName1 AS NVARCHAR(100)) + = a.SomeColumn
FROM #MyTemp x INNER JOIN dbo.AnotherTable a (NOLOCK) ON a.ColumnName = x.ColumnName
WHERE somecondition;
EXEC(#DynamicSQL)
If you place the Alter and the Update in the same execution it won't work, they can't be executed at the same time, otherwise is the same problem as the static code all over again. This way is like a simulation of the extra call to another stored proc but without having an extra proc.
The stored procedure is going to have a different scope that when you try to update it after the proc runs. You will need your alter statement to be inside the proc for this to take affect, or bring the contents of the stored proc outside of the stored procedure so your temp table will share the same scope as your update statement.
For example:
CREATE PROC SPX_TEST
AS
SELECT 1 AS ABC INTO #TEMP_A
EXEC SPX_TEST
SELECT * FROM #TEMP_A
Generates an error saying the temp table does not exist.

How to use nested If statements in SQL trigger

I'm trying to learn SQL triggers to automatically handle events in my database but I'm having some problems with execution.
If I run the following code:
declare #userid numeric(18,0);
declare #username nvarchar(max);
set #userid = 400
execute GetUserNameFromID #userid,#username output
select #username
which calls the following stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetUserNameFromID
-- Add the parameters for the stored procedure here
#UserID numeric(18,0),
#UserName nvarchar(MAX) OUT
AS
BEGIN
SET NOCOUNT ON;
SELECT #UserName = u.name from Users u where ID=#UserID
END
GO
I get a nice result 'sometestuser'
But when calling it from my trigger it fails to return a value from the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Trigger [dbo].[CheckIfUserHasNoItemsLeft] on [dbo].[Items] for update
As
Begin
set nocount on
declare #inactive_user nvarchar(50);
declare #userid numeric(18,0);
declare #username nvarchar(MAX);
if ( select Count(*) from inserted ) > 1 RaIsError( 'CheckIfIserHasNoItemsLeft: No more than one row may be processed.', 25, 42 ) with log
if update(InactiveUser)
set #inactive_user = (select InactiveUser from inserted)
if #inactive_user is not null
set #userid = (select CID from inserted)
execute GetuserNameFromID #userid,#username output
if #username is not null
insert into tasks (Task) values ('The last item for ' + #username + ' has been marked inactive, check if this user should now be also marked inactive.')
End
InactiveUser is the name of the app user who has marked this item inactive, it is what I am using as a check to see if the item has been set inactive rather than create an additional boolean column just for this purpose.
I'm sure it's something simple but information on If...Then statements for SQL seems to be limited and a lot of answers suggest using Case but the query editor gives me errors about incorrect syntax no matter which way I try to do it that way.
As I'm learning I'm more than happy for someone to show me a completely new way of handling this if what I've done is wrong or bad design. I'm hoping to create a set of triggers that will add items to the tasks table for administrators to check when user accounts appear to be stale and other maintenance checks etc.
I am using SQL server 2005.
Thank you.
Edit: Changed 'value <> null' to 'value is not null'
Edit2: Added HABO's suggestion to throw an error if more than one row is detected.
How about we take a whole new approach to this. Processes like this are exactly why the inline table valued functions were created.
Let's start by converting your stored procedure to an inline table valued function.
CREATE FUNCTION GetUserNameFromID
(
#UserID numeric(18,0)
) RETURNS TABLE
AS RETURN
SELECT u.name
from Users u
where ID = #UserID
GO
That is a LOT simpler and cleaner than that stored procedure with an output variable.
Here is where it really starts to make a difference. Here is what you could do with that trigger using the newly created iTVF.
ALTER Trigger [dbo].[CheckIfUserHasNoItemsLeft] on [dbo].[Items] for update
As Begin
set nocount on
if update(InactiveUser)
insert into tasks (Task)
select 'The last item for ' + u.name + ' has been marked inactive, check if this user should now be also marked inactive.'
from inserted i
cross apply dbo.GetUserNameFromID(i.CID) u
end
This is super simple AND it is fully set based so if you update 1 or 1,000 rows it will work correctly.

Creating a stored procedure using variables

Is there any good way to do this, or am I just heading in the wrong direction? I would like to create a stored procedure inside an SQL script. I would like to have variables declared at the beginning of the script so that I can create the SPROCs to use in different contexts/servers.Here is what I would like to do (I know this obviously doesn't work, but I'm looking for any ideas of an alternative)..
DECLARE #golbalValue = 'SomeValue'
GO
CREATE PROCEDURE [dbo].[MyStoredProcedure](
AS
BEGIN
SELECT * FROM Mytable WHERE MyCol = #globalValue
END
GO
What you could do is use a scalar function for the variable
create function f ()
returns varchar(20)
as
begin
return 'some value'
end
go
then use it in your procedure
create proc p ()
as
begin
select *
from my_table
where col = f()
end
go
another possibility which is perhaps more appropriate is to use sqlcmd here's an example.
From what I understand, you need to create stored procedures with set value from your parameters. You don't want input parameters in the stored Procedures though. Second, you want to switch database contexts. So I think you'll need a tempTable for your parameters and some dynamic SQL. Try this out:
IF OBJECT_ID('tempdb..#globalParam') IS NOT NULL
DROP TABLE #globalParam;
IF OBJECT_ID('AdventureWorks2012.dbo.myTable') IS NOT NULL
DROP TABLE AdventureWorks2012.dbo.myTable
IF OBJECT_ID('Master..myTable') IS NOT NULL
DROP TABLE Master..mytable
--Create your data tables
SELECT 'SomeValue' AS col1 INTO AdventureWorks2012.dbo.myTable;
SELECT 1000 AS col1 INTO master.dbo.myTable;
CREATE TABLE #globalParam(
ParamName VARCHAR(100),
val SQL_VARIANT --SQL_Variant is designed to hold all data types.
);
--Here are your globalParams
DECLARE #globalParam1 VARCHAR(100) = 'SomeValue';
DECLARE #globalParam2 INT = 1000;
--Load your parameters into a table. Might have to cast some of your parameters to SQL_Variant
INSERT INTO #globalParam
VALUES ('globalParam1',#globalParam1),
('globalParam2',CAST(#globalParam2 AS sql_variant));
GO
--Switch database context
USE AdventureWorks2012
GO
--Variable to hold CREATE PROC
DECLARE #sql VARCHAR(MAX);
--Set #SQL with parameter value from #globalParam
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam1'
--Execute to create the stored procedure
EXEC(#sql)
--Execute it to see if it works
EXEC dbo.myStoredProc
--Switch context. Repeat same steps
USE master
GO
DECLARE #sql VARCHAR(MAX);
SELECT #sql =
'CREATE PROCEDURE dbo.myStoredProc AS
BEGIN
SELECT * FROM myTable WHERE col1 = ''' + CAST(val AS VARCHAR(100)) + '''
END'
FROM #globalParam
WHERE ParamName = 'globalParam2'
EXEC(#sql)
EXEC dbo.myStoredProc
--Cleanup
DROP PROCEDURE dbo.myStoredProc;
USE AdventureWorks2012
GO
DROP PROCEDURE dbo.myStoredProc;
You cannot do what you want. T-SQL doesn't have the concept of global variables. One method is to store values in a "global" table and then reference them as needed. Something like:
create table GlobalParams (
name varchar(255) not null primary key,
value varchar(255) not null
);
create procedure . . .
begin
. . .
declare #value varchar(255);
select #value = value from Globalparams where name = 'name';
select *
from Mytable
where MyCol = #value;
. . .
end;
Note: this is a simplistic example that only allows variables whose type is a string.
You can also wrap the logic in a user-defined function, so the call looks like:
select *
from Mytable
where MyCol = udf_GlobalLookup('name');
It is rather rare to need global parameters that are shared among different stored procedures. Such a global context can be useful, at times, for complex systems. It is unlikely that you need all this machinery for a simple application. An alternative method, such as just passing the parameters in as arguments, is probably sufficient.

Create one of two possible store procedures in one script

I want to do a check (if statement) and then create one of two possible procedures.
Right now I am trying to use a IF EXISTS statement inside a CREATE PROCEDURE statement.
CREATE PROCEDURE [dbo].[TestProc]
AS
SET NOCOUNT ON
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER OFF
IF EXISTS (select * from table)
BEGIN
DECLARE #region NVARCHAR(100)
SELECT *
INTO #TempTable
FROM User
...do something with #TempTable etc..
DROP #TempTable
END
ELSE
BEGIN
DECLARE #region NVARCHAR(100)
SELECT *
INTO #TempTable
FROM User
...do something else with #TempTable etc
DROP #TempTable
END
I get the following 2 errors
There is already an object named 'TempTable' in the database.
The variable name '#region' has already been declared. Variable names must be unique within a query batch or stored procedure.
Either a) Create the temp table first in the stored procedure, then use INSERT INTO (...) SELECT ... to populate it, or b) Use a different name for the temp table in the two branches.
The T-SQL parser is a remarkably simple beast, and control flow doesn't affect its interpretation of which objects exist or not - so if you declare a temp table in one branch of an IF, you can't declare it separately in the other branch - it "exists" whether the control flow enters the IF branch or not.
A Similar argument applies for variables.
Obviously, option a only works if the temp table structures are identical.
Simple example of how control flow is ignored:
if 1=2
begin
declare #a int
end
set #a = 5
print #a
prints 5. Just:
set #a = 5
print #a
produces the error Must declare the scalar variable "#a"., which demonstrates that the declaration (inside the un-followed branch of if) still took effect.
1.You need use two different temp table and before SELECT * INTO statements DROP them
2.Variable need once declared in the body procedure. In your case before IF EXISTS
CREATE PROCEDURE [dbo].[TestProc]
AS
SET NOCOUNT ON
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER OFF
DECLARE #region NVARCHAR(100)
IF EXISTS(SELECT 1 FROM dbo.test6)
BEGIN
IF OBJECT_ID('tempdb.dbo.#TempTable') IS NOT NULL DROP TABLE dbo.#TempTable
SELECT *
INTO #TempTable
FROM dbo.test6
END
ELSE
BEGIN
IF OBJECT_ID('tempdb.dbo.#TempTable2') IS NOT NULL DROP TABLE dbo.#TempTable2
SELECT *
INTO #TempTable2
FROM dbo.test6
END

How to query from a stored procedure in SQL Server?

Let say I have a simple Stored Procedure:
ALTER PROCEDURE [dbo].[myProc]
AS
BEGIN
SELECT * FROM myTable
END
How can I do a WHERE statement in Microsoft SQL Server Management Studio to the stored procedure? Something like that:
SELECT * FROM myProc WHERE x = 'a'; -- But that doesn't work...
It sounds like you're trying to make a "dynamic" stored procedure.
Something you might want to do is:
1) Insert the contents of your stored procedure into a temporary table
2) Use dynamic sql to apply a where condition to that temporary table.
Something like:
declare #as_condition varchar(500); --Your condition
create table #a
(
id bigint
)
insert into #a
execute sproc
declare #ls_sql varchar(max);
set #ls_sql = "select * from #a where " + #as_condition;
execute (#ls_sql);
SQL Server allows you to use INSERT INTO to grab a stored procedure's output. For example, to grab all processes with SPID < 10, use:
create table #sp_who (
spid smallint,
ecid smallint,
status nchar(30),
loginame nchar(128),
hostname nchar(128),
blk char(5),
dbname nchar(128),
cmd nchar(16),
request int)
insert into #sp_who execute sp_who
select * from #sp_who where spid < 10
You can't add a WHERE clause to a stored procedure like this.
You should put the clause in the sproc, like this:
ALTER PROCEDURE [dbo].[myProc]
#X VARCHAR(10)
AS
BEGIN
SELECT * FROM myTable WHERE x=#X
END
GO
The syntax for calling a stored procedure is through the use of EXECUTE not SELECT(e.g.):
EXECUTE dbo.myProc 'a'
I think you can't do that.
The command to execute a stored procedure is EXECUTE.
See some more examples of the EXECUTE usage.
I think its better to use a view or a table valued function rather than the suggested approach. Both allow you to pass parameters to the function
If you want the WHERE clause to be something you can "turn off" you can do this, passing in a predetermined value (e.g. -1) if the WHERE limitation is to be bypassed:
ALTER PROCEDURE [dbo].[myProc]
#X VARCHAR(10)
AS
BEGIN
SELECT * FROM myTable WHERE x=#X or #X = -1
END
GO
You must declare a variable in the store procedure which will be necessary to pass to run the stored procedure. Here is an example. Keep this in mind: Before AS you can simply declare any variable by using the # character, but after the AS you must write Declare to declare any variable, e.g., Declare #name nvarchar (50).
ALTER PROCEDURE [dbo].[myProc]
#name varchar (50)
AS
BEGIN
SELECT * FROM myTable
where name= #name
END