TSQL SET command not working inside EXEC statement - sql

I am trying to run the below script in SQL Server Management Studio:
DECLARE #counter INT = 1
EXEC('SET '+#counter+' = '+#counter+' + 1')
However, I get this error:
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '1'-
I have been troubleshooting for days but gotten nowhere - I guess most likely TSQL simply does not accept this inside an EXEC command, but what could I potentially replace it with?
This is a part of a larger script where the above MUST be inside an EXEC statement in order for the rest to work..

This isn't going to working for a number of reasons.
Let's start with what you have:
DECLARE #counter INT = 1
EXEC('SET '+#counter+' = '+#counter+' + 1')
EXEC is expecting a string inside the parenthesis, yet you have an int (the variable #counter), so that is your first problem. Oddly, however, you aren't getting a conversion error here; it seems that #counter is being interpreted as its value (1), rather than Data Type Precedence being applied first. (If you try SELECT 'SET '+#counter+' = '+#counter+' + 1' you'll get the error "Conversion failed when converting the varchar value 'SET ' to data type int.").
Even if the query successfully got past that hurdle, that would cause another error because the query becomes:
SET 1 = 1 + 1
Obviously that isn't going to work, 1 is 1, no other number.
Next, a variable declared outside of the scope of a "dynamic" statement can't be referenced inside it. Let's take the following:
DECLARE #Varible int = 1;
EXEC (N'SELECT #Variable;');
This returns the error:
Must declare the scalar variable "#Variable".
If you are using a "dynamic" statement (your statement isn't dynamic at all), then you need to parametrise the variables. This is why you shouldn't be using EXEC ({SQL Statement}) and should be using sys.sp_executesql.
So, for the above, you get:
DECLARE #counter INT = 1;
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SET #Variable = #Variable + 1;';
EXEC sys.sp_executesql #SQL, N'#Variable int OUTPUT',#Variable = #counter OUTPUT;
SELECT #Counter;
As I mentioned, that is all pointless, as there's nothing dynamic in the above, and your query should just be:
DECLARE #counter INT = 1;
SET #counter = #counter + 1;
Of course, none of this even goes on to address things like how to safely inject objects into a dynamic statement, but that isn't what this question is about. As I mention, the dynamic statement here is redundant, as it isn't dynamic, and it's not that required deferred parsing (such as when you ALTER a table to add a column, and then try to reference that column in the same batch).

If you run the script below, it'll throw an error like
Msg 137, Level 15, State 1, Line 1
Must declare the scalar variable "#counter".
BEGIN
DECLARE #counter INT = 1
EXEC('SET #counter = '+ #counter+' + 1')
END
WHY?
Because dynamic SQL is a different scope to the outer.
So can try this way (using output)
DECLARE #counter INT = 1
DECLARE #SQL nvarchar(50) = 'SELECT #counter2 =' + CAST(#counter + 1 AS VARCHAR(100))
exec sp_executesql #SQL, N'#counter2 int out', #counter out
SELECT #counter

Related

SELECT statement in while loop isn't working

I have created the following Select statement which is working fine:
SELECT #Block = [Blok], #Year = [Aar]
FROM [PT99991_Result].[dbo].[Testheader]
WHERE N = #TestHeaderID
The problem is that this Select statement is used in a While loop where the database can change to another one during the loop. I have tried to modify the statement to the following but it's not working. I have also tried to use EXEC which takes care of my problem but then I'm facing a problem with the local variables #Block and #Year instead.
SET #DataBase = 'PT99991_RESULT' --This is only for test!
SELECT #Block = [Blok], #Year = [Aar]
FROM '[' + #DataBase + '].[dbo].[Testheader]'
WHERE N = #TestHeaderID
I'm not sure what I'm doing wrong?
First, generate a T-SQL template like this:
DECLARE #DynamicTSQLStatement NVARCHAR(MAX) = N'SELECT #Block = [Blok], #Year = [Aar]
FROM [#DataBase].[dbo].[Testheader]
WHERE N = #TestHeaderID';
Then, let's say that the variale #Datbase holds your current database name. If it is not extracted from sys.database you can perform additional validation to ensure nobody is doing something wrong.
IF NOT EXISTS(SELEFT 1 FROM sys.database WHERE [name] = #Datbase
BEGIN
....
END;
After the validation, you just replace the database name in the template:
SET #DynamicTSQLStatement = REPLACE(#DynamicTSQLStatement, '#Database', #Database);
Then, execute the code passing the parameters:
EXEC sp_executesql #DynamicTSQLStatement
,N'#TestHeaderID INT'
,N'#Block INT OUTPUT'
,N'#Year INT OUTPUT'
,#TestHeaderID
,#Block OUTPUT
,#Year OUTPUT;
of course on every loop iteration, reset the template.
Instead of while loop, You can go for undocumented stored procedure: ms_foreachdb and execute against the databases and finally apply filter for the specific database.
Caveat: Don't use this in production code, because, it uses undocumented stored procedure.
CREATE TABLE #test(dbname sysname, blok int, aar int)
DECLARE #db_list NVARCHAR(max) = 'DB1,DB2'
EXECUTE master.sys.sp_MSforeachdb 'USE [?]; declare #blok int, #aar int; INSERT INTO #test SELECT db_name(), blok, aar from [dbo].[Testheader] WHERE N = TestHeaderId;'
SELECT * FROM #test where dbname in
(
SELECT value FROM string_split(#db_list,',')
)

Problem with a dynamic function in SQL Server

I have a table dbo.t_products and I want to know the last record updated. For that I have an attribute last_changed which stores for each record the timestamp of the last update.
Finally I want to save that result in a variable called #y.
DECLARE #y DATETIME
DECLARE #p_table VARCHAR(100)
SET #p_table = 'dbo.t_products'
EXECUTE sp_executesql N'SET #y = SELECT MAX(last_changed) FROM #p_table'
,N'#p_table VARCHAR, #y DATETIME OUTPUT'
,#p_table
,#y OUTPUT
SELECT #y
The system returns the following message:
Msg 156, Level 15, State 1, Line 25
Incorrect syntax near the keyword 'SELECT'.
Any ideas?
The whole point of using dynamic SQL in your case (I assume) is to allow the use of a dynamic table name. In which case you have to insert the table name into the dynamic SQL string - you can't pass it in as a parameter, thats the problem you are trying in the first place.
Also you don't need a SET followed by a SELECT just use SELECT directly to set the variable.
Finally you definitely want to use the QUOTENAME function to escape your table name and avoid an SQL injection attack - this requires you split the table name and schema name.
DECLARE #y DATETIME;
DECLARE #p_schema VARCHAR(100);
DECLARE #p_table VARCHAR(100);
DECLARE #SQL NVARCHAR(max);
SET #p_schema = 'dbo';
SET #p_table = 't_products';
-- Add the table name to the dynamic SQL
SET #SQL = 'SELECT #y = MAX(last_changed) FROM ' + QUOTENAME(#p_schema) + '.' + QUOTENAME(#p_table);
EXECUTE sp_executesql #SQL, N'#y DATETIME OUTPUT', #y OUTPUT;
-- PRINT(#SQL); --- Debugging
SELECT #y;

T-SQL: can't seem to set variables immediately after declaring them

I have looked through countless posts on declaring/setting variables in a sql script. But most seem to involve syntax errors or using exec commands. The script I am trying to execute is quite simple - so I am having a hard time understanding why I cant set values.
Here is the SQL:
declare #counter int = 1,
#batchSize int = 100000,
#tableRows int,
#totalBatches int;
set #tableRows= (select count(distinct chFileVersionID) from IRISDocuments)
set #totalBatches = (ceiling((#tableRows / #batchSize)));
--print some stuff
while #counter <= #totalBatches
begin
. . . loop logic that only uses #counter variable for incrementing
end
The error I get is
Must declare the scalar variable "#tableRows"
which is clearly declared directly above. I have tried setting values with a select statement, as well as declaring each variable individually, and declaring and setting value in the same statement with no avail.
The above actually works (I mean testing without the WHILE loop of course).
2 possible source of the error if the variable is not exactly used as above:
Using a variable to define another variable in the same declaration
E.g.:
declare #counter int = 1,
#batchSize int = 100000,
#tableRows int = 10000000000000,
#totalBatches int = (ceiling((#tableRows / #batchSize)))
This won't work and produces your error - within a statement the order of execution is not row-by-row, so you can not give a value to #totalBatches as at that point #tableRows is not implemented yet.
Trying to use a variable that is out of scope
If this is a query that contains the GO batch separator then after that all variables before GO are out of scope.
E.g.:
declare #counter int = 1,
#batchSize int = 100000,
#tableRows int,
#totalBatches int;
SELECT #counter, #batchSize, #tableRows, #totalBatches int
GO
SELECT #counter, #batchSize, #tableRows, #totalBatches int
The second SELECT will throw your error.

Must declare the scalar variable #Stringkey

I am trying to create a SQL script that fills a table with a new primary key.
what i currently have from internet searches is the following
DECLARE #IntVariable int;
DECLARE #Stringkey varchar(2);
DECLARE #SQLString nvarchar(500);
DECLARE #Intupdater nvarchar(500);
SET #IntVariable = 00000001;
SET #Stringkey = 'CD';
SET #SQLString = N'UPDATE Tri_Damen_Live.trimergo.calendar_details SET details_key = #Stringkey + #IntVariable';
SET #Intupdater = N'#IntVariable = #IntVariable+1'
EXECUTE sp_executesql #SQLString , N'#IntVariable INT' , N'#Stringkey VARCHAR(2)', #IntVariable, #Stringkey;
The errors I get with this are
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '='.
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#Stringkey".
I have a few questions about my problem and hope someone might be able to help me
First question : it seems my int increaser is wrong. is there a way to make a update variable statement for the int?
Second : I declared the int as 00000001. But I doubt it stays like this. The reason I do this is because the key needs to be a set 10 characters long in total (example 'CD00000001' etc) does this work like this or is there another way?
Third question : as the topic stated. The 'Must declare the scalar variable "#Stringkey".' error. I have removed the stringkey part but then it says the same error about the Int variable. I declared them at the top and set them as well. What am I missing?
Side information :
this function will be used for multiple databases with varying table sizes. So I need this kind of function to account that it does not have a set amount of update statements.
The primary key that is created here must be a string (Varchar(10)) that is ALWAYS 10 characters long. Seems like a silly requirement but it has been chosen to use this system.
I am using Microsoft SQL Server 2012 to run this in.
1 You can't add an int to a string like that. You need to convert the int to a string
#Stringkey + convert(varchar(10),#IntVariable)
2 An int is a number. If you want it to be formatted, you need to do that yourself.
SET details_key = #Stringkey + right(''0000000000''+convert(varchar(8),#IntVariable),8)
3 Your variable declaration in sp_executesql should be
N'#IntVariable INT, #Stringkey VARCHAR(2)'
ie
EXECUTE sp_executesql #SQLString , N'#IntVariable INT, #Stringkey VARCHAR(2)', #IntVariable, #Stringkey;
Alternatively, you might consider using a SEQUENCE - see http://raresql.com/2013/06/21/sql-server-2012-how-to-generate-a-varchar-sequence-number-using-sequence-object/
The second parameter to sp_executesql should be a single string containing all parameter definitions:
EXECUTE sp_executesql #SQLString , N'#IntVariable INT,#Stringkey VARCHAR(2)', #IntVariable, #Stringkey;
SET #SQLString = N'UPDATE Tri_Damen_Live.trimergo.calendar_details SET details_key = #Stringkey + #IntVariable';
You are trying to concat #Stringkey + #IntVariable => (string + int) where you should convert the #IntVariable to varchar

Must declare the scalar variable

#RowFrom int
#RowTo int
are both Global Input Params for the Stored Procedure, and since I am compiling the SQL query inside the Stored Procedure with T-SQL then using Exec(#sqlstatement) at the end of the stored procedure to show the result, it gives me this error when I try to use the #RowFrom or #RowTo inside the #sqlstatement variable that is executed.. it works fine otherwise.. please help.
"Must declare the scalar variable "#RowFrom"."
Also, I tried including the following in the #sqlstatement variable:
'Declare #Rt int'
'SET #Rt = ' + #RowTo
but #RowTo still doesn't pass its value to #Rt and generates an error.
You can't concatenate an int to a string. Instead of:
SET #sql = N'DECLARE #Rt int; SET #Rt = ' + #RowTo;
You need:
SET #sql = N'DECLARE #Rt int; SET #Rt = ' + CONVERT(VARCHAR(12), #RowTo);
To help illustrate what's happening here. Let's say #RowTo = 5.
DECLARE #RowTo int;
SET #RowTo = 5;
DECLARE #sql nvarchar(max);
SET #sql = N'SELECT ' + CONVERT(varchar(12), #RowTo) + ' * 5';
EXEC sys.sp_executesql #sql;
In order to build that into a string (even if ultimately it will be a number), I need to convert it. But as you can see, the number is still treated as a number when it's executed. The answer is 25, right?
In your case you can use proper parameterization rather than use concatenation which, if you get into that habit, you will expose yourself to SQL injection at some point (see this and this:
SET #sql = #sql + ' WHERE RowNum BETWEEN #RowFrom AND #RowTo;';
EXEC sys.sp_executesql #sql,
N'#RowFrom int, #RowTo int',
#RowFrom, #RowTo;
You can also get this error message if a variable is declared before a GOand referenced after it.
See this question and this workaround.
Just FYI, I know this is an old post, but depending on the database COLLATION settings you can get this error on a statement like this,
SET #sql = #Sql + ' WHERE RowNum BETWEEN #RowFrom AND #RowTo;';
if for example you typo the S in the
SET #sql = #***S***ql
sorry to spin off the answers already posted here, but this is an actual instance of the error reported.
Note also that the error will not display the capital S in the message, I am not sure why, but I think it is because the
Set #sql =
is on the left of the equal sign.
Sometimes, if you have a 'GO' statement written after the usage of the variable, and if you try to use it after that, it throws such error. Try removing 'GO' statement if you have any.
This is most likely not an answer to the issue itself, but this question pops up as first result when searching for Sql declare scalar variable hence I want to share a possible solution to this error.
In my case this error was caused by the use of ; after a SQL statement. Just remove it and the error will be gone.
I guess the cause is the same as #IronSean already posted in a comment above:
it's worth noting that using GO (or in this case ;) causes a new branch where declared variables aren't visible past the statement.
For example:
DECLARE #id int
SET #id = 78
SELECT * FROM MyTable WHERE Id = #var; <-- remove this character to avoid the error message
SELECT * FROM AnotherTable WHERE MyTableId = #var
Just adding what fixed it for me, where misspelling is the suspect as per this MSDN blog...
When splitting SQL strings over multiple lines, check that that you are comma separating your SQL string from your parameters (and not trying to concatenate them!) and not missing any spaces at the end of each split line. Not rocket science but hope I save someone a headache.
For example:
db.TableName.SqlQuery(
"SELECT Id, Timestamp, User " +
"FROM dbo.TableName " +
"WHERE Timestamp >= #from " +
"AND Timestamp <= #till;" + [USE COMMA NOT CONCATENATE!]
new SqlParameter("from", from),
new SqlParameter("till", till)),
.ToListAsync()
.Result;
Case Sensitivity will cause this problem, too.
#MyVariable and #myvariable are the same variables in SQL Server Man. Studio and will work. However, these variables will result in a "Must declare the scalar variable "#MyVariable" in Visual Studio (C#) due to case-sensitivity differences.
Just an answer for future me (maybe it helps someone else too!). If you try to run something like this in the query editor:
USE [Dbo]
GO
DECLARE #RC int
EXECUTE #RC = [dbo].[SomeStoredProcedure]
2018
,0
,'arg3'
GO
SELECT month, SUM(weight) AS weight, SUM(amount) AS amount
FROM SomeTable AS e
WHERE year = #year AND type = 'M'
And you get the error:
Must declare the scalar variable "#year"
That's because you are trying to run a bunch of code that includes BOTH the stored procedure execution AND the query below it (!). Just highlight the one you want to run or delete/comment out the one you are not interested in.
If someone else comes across this question while no solution here made my sql file working, here's what my mistake was:
I have been exporting the contents of my database via the 'Generate Script' command of Microsofts' Server Management Studio and then doing some operations afterwards while inserting the generated data in another instance.
Due to the generated export, there have been a bunch of "GO" statements in the sql file.
What I didn't know was that variables declared at the top of a file aren't accessible as far as a GO statement is executed. Therefore I had to remove the GO statements in my sql file and the error "Must declare the scalar variable xy" was gone!
As stated in https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go?view=sql-server-ver16 , the scope of a user-defined variable is batch dependent .
--This will produce the error
GO
DECLARE #MyVariable int;
SET #MyVariable = 1;
GO --new batch of code
SELECT #MyVariable--CAST(#MyVariable AS
int);
GO
--This will not produce the error
GO
DECLARE #MyVariable int;
SET #MyVariable = 1;
SELECT #MyVariable--CAST(#MyVariable AS int);
GO
We get the same error when we try to pass a variable inside a dynamic SQL:
GO
DECLARE #ColumnName VARCHAR(100),
#SQL NVARCHAR(MAX);
SET #ColumnName = 'FirstName';
EXECUTE ('SELECT [Title],#ColumnName FROM Person.Person');
GO
--In the case above #ColumnName is nowhere to be found, therefore we can either do:
EXECUTE ('SELECT [Title],' +#ColumnName+ ' FROM Person.Person');
or
GO
DECLARE #ColumnName VARCHAR(100),
#SQL NVARCHAR(MAX);
SET #ColumnName = 'FirstName';
SET #SQL = 'SELECT ' + #ColumnName + ' FROM Person.Person';
EXEC sys.sp_executesql #SQL
GO
Give a 'GO' after the end statement and select all the statements then execute