Concatenation problem in Dynamic sql query - sql

I'm trying to concatenate the string with multiple variables and go for exec. Unfortunately I'm facing the conversion problem as:
Conversion failed when converting the varchar value 'Select #ExistingIds= CBSE_IX_J from ##tempPivot where EmployeeID=' to data type int.
My query is :
SET #ExecColumn = concat('Select #ExistingIds= '+#TSectionName +' from ##tempPivot where EmployeeID='+CAST(#TUserID as INT),'')
PRINT #ExecColumn
EXEC (#ExecColumn)

The "simple" answer is don't concatenate raw string values into your dynamic statement, and parametrise your code. This is a bit of guesswork, however, is far safer than the SQL Injection hole you have right now:
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT #ExistingIDs = ' + QUOTENAME(#TSectionName) + NCHAR(13) + NCHAR(10)+
N'FROM ##tempPivot' + NCHAR(13) + NCHAR(10) +
N'WHERE EmployeeID = #TUserID;';
PRINT #SQL;
EXEC sp_executesql #SQL,
N'#TUserID int, #ExistingIds int OUTPUT', --guessed datatypes and that #ExistingIds is an OUTPUT
#TUserID = #TUserID,
#ExistingIds = ExistingIds OUTPUT;
Note: the fact that your variable is called #ExistingIDs implies you want to store multiple values in that variable. #ExistingIDs is a scalar value, it will only hold a scalar (single) value. If the query above returns multiple rows, only the value of from the last row will be returned. For example:
DECLARE #i int;
SELECT #i = I
FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9))V(I)
ORDER BY I;
SELECT #i;
Notice that #i has the value 9, not '1,2,3,...,9'.

You would seem to want:
DECLARE #SQL nvarchar(MAX);
DECALRE #ExistingIds NVARCHAR(MAX);
SET #SQL = N'
SELECT #ExistingIDs = STRING_AGG(#TSectionName, '''')
FROM ##tempPivot
WHERE EmployeeID = #TUserID
';
-- Cannot have identifiers as parameters, so use `REPLACE()`
SET #SQL = REPLACE(#SQL, '#TSectionName', QUOTENAME(#TSectionName);
EXEC sp_executesql #SQL,
N'#TUserID int, #ExistingIds NVARCHAR(MAX) OUTPUT', --guessed datatypes and that #ExistingIds is an OUTPUT
#TUserID=#TUserID,
#ExistingIds=#ExistingIds OUTPUT;
In older versions of SQL Server, you need another approach for concatenating strings. For instance
SET #SQL = N'
SELECT #ExistingIDs = (SELECT #TSectionName
FROM ##tempPivot
WHERE EmployeeID = #TUserID
FOR XML PATH ('')
)
';

Related

Error in dynamic execute with local variable

I'm getting the error :
Procedure expects parameter '#params' of type 'ntext/nchar/nvarchar'
for the following execute statement. I'm a little confused with this, after reading several posts on the website,
I came to understand that I would need to pass #val as a variable since exec will execute the command as-is.
declare #val varchar(max);
declare #query nvarchar(max);
set #query = 'set #val = (select ' + #cols + ' from temp where loan_number = ''72'')'
exec sp_executesql #query, '#val varchar(max) output', #val output
Conversely, if I executed the exec as a non-dynamic query as just exec(#query), I get the error
Must declare the scalar variable "#val".
IF the statement "Should the output of the query be a 1x1 table since I" is true, then you need to use a SELECT to set the value:
DECLARE #val varchar(MAX);
DECALRE #query nvarchar(MAX);
SET #query = N'SELECT #val = ' + QUOTENAME(#cols) + N' FROM temp WHERE loan_number = ''72'');';
EXEC sp_executesql #query, N'#val varchar(MAX) OUTPUT', #val OUTPUT;
The fact that you have a variable called #cols implies that the variable will hold the names of many columns, not one.

How to Use Dynamic Sql on Update Command?

I Had Update Command. Just Like this.
Update DispatchExceptions
SET TYPE='MWM'
WHERE ID=9801246
I want to fire same command using dynamic sql.
DECLARE #ColumnName varchar(20)=N'TYPE'
DECLARE #ColumnValue char(3)='MWM'
DECLARE #ID INTEGER = 9801246
declare #Query nvarchar(max)
SET #Query = 'Update DispatchExceptions SET '+ QUOTENAME(#ColumnName) + '''=' + #ColumnValue + '''WHERE ID =' + #ID +''
EXEC (#Query)
But it show following error.
Conversion failed when converting the nvarchar value 'Update DispatchExceptions SET [TYPE]'=MWM'WHERE ID =' to data type int.
How can I use dynamic sql in the situation. Any suggestion.
The problem here is specifically the part ' + #ID. #ID is the datatype int, which has a higher datatype precedence than nvarchar; thus any other expressions will be implicitly cast to an int (and as I'm sure you're aware, 'Update DispatchExceptions SET...' isn't an int).
If you parametrise your query, you won't have this problem:
DECLARE #ColumnName sysname = N'TYPE'; --Changed the datatype here to sysname, as the caters for all possible object names
--sysname is actually a synonym for nvarchar(128), and 128 is the maximum length for an object's name
DECLARE #ColumnValue char(3) = 'MWM';
DECLARE #ID integer = 9801246;
DECLARE #Query nvarchar(MAX);
SET #Query = N'Update DispatchExceptions SET ' + QUOTENAME(#ColumnName) + N' = #value WHERE ID = #ID;';
EXEC sp_executesql #Query,
N'#Value char(3), #ID int',
#Value = #ColumnValue,
#ID = #ID;
Also, to the OP, well done on the use of QUOTENAME. This is far too often missed out, and thus leaves your (dynamic) SQL open to injection.
The DBMS fails to add
9801246
to
Update DispatchExceptions SET [TYPE]'=MWM'WHERE ID =
because the latter is a string and canot be converted to number, which would be necessary in order to add the number 9801246 to it :-)
One solution:
DECLARE #ID varchar(7) = '9801246'
Another solution:
SET #Query = 'Update DispatchExceptions SET '+ QUOTENAME(#ColumnName) +
'''=' + #ColumnValue + '''WHERE ID =' + CAST(#ID AS VARCHAR(7)) +''
Simply cast your #ID as varchar and correct quotation before #ColumnValue as given below:
SET #Query = 'Update DispatchExceptions SET '+QUOTENAME(#ColumnName)+'='''+#ColumnValue+''' WHERE ID ='+cast(#ID as varchar)+'';

SQL add a variable to a query

How do I create variables that are specified once and then used in queries later in a script? These variables may be used multiple times in a query, and in multiple queries in a script. I use #x as such a variable in the examples below.
What I want to do is something like:
Declare #Query nvarchar(1000)
Declare #x nvarchar(40)
Set #x = 'test'
Set #Query = 'Select [Name]
, ' + #x + ' as [TestCase]
From mytable'
Exec (#Query)
-- returns "Invalid column name 'test'"
Which returns the error mentioned above. I would like it to achieve the equivalent of:
Declare #Query nvarchar(1000)
Declare #x nvarchar(40)
Set #x = 'test'
Set #Query = 'Select [Name]
, ''test'' as [TestCase]
From mytable'
Exec (#Query)
-- Returns e.g.
-- Name TestCase
-- Alice Test
-- Bob Test
I also note that the following doesn't work and returns the same error as the first:
Declare #Query nvarchar(1000)
Declare #x nvarchar(40)
Set #x = 'test'
Set #Query = 'Select [Name]
, ' + 'test' + ' as [TestCase]
From mytable'
Exec (#Query)
-- returns "Invalid column name 'test'"
Based on the error and since I'm not trying to use the #x as a column name, but just as a variable, I assume I'm using an invalid implementation of a variable.
Since you're not trying to use a variable as a column name, you do not need to use dynamic SQL at all. (Which is a Good Thing(TM) since dynamic SQL should only be used with a great deal of caution due to it being a great attack surface.)
A simple:
declare #x nvarchar(40)
set #x = 'test'
select [Name], #x as TestCase
from mytable
will do.
That being said, if you have a use case for dynamic SQL (again the particular query in question here does not but perhaps an ad-hoc query is being passed in to the procedure), the thing to do would be to pass your variable as a parameter to the query via sp_executesql. This is akin to creating a stored procedure with parameters:
declare #x nvarchar(40)
declare #query nvarchar(1000)
set #x = 'test'
set #query = 'select [Name], #x as TestCase from mytable'
exec sp_executesql #query, N'#x nvarchar(1000)', #x
You were missing quotes. Thats it. Try below code.
Declare #Query nvarchar(1000)
Declare #x nvarchar(40)
Set #x = 'test'
Set #Query = 'Select [Name]
, ''' + #x + ''' as [TestCase]
From mytable'
Exec (#Query)
Declare #Query nvarchar(1000)
Declare #x nvarchar(40)
Set #x = 'test'
Set #Query = 'Select [Name],'++''''+#x+''''+ ' as [TestCase]
From mytable'
print #query
Output:
Select [Name],'test' as [TestCase]
From mytable

SQL Linked Server query with Parameters

I need to select value from SQL Linked Server & get it to loacal variable
This is what I've written so far:
DECLARE #SQLQUERY AS VARCHAR(1000)
DECLARE #FINALQUERY AS VARCHAR(1000)
DECLARE #OutVal AS VARCHAR(10)
SET #SQLQUERY = 'SELECT Field1 FROM Table1 WHERE Field2=' + CAST(#var1 AS VARCHAR)
SET #FINALQUERY = 'SELECT #OutVal=Field1 FROM OPENQUERY(LINKEDSERVER,' + '''' + #SQLQUERY + '''' + ')'
EXEC(#finalQuery)
but this is wrong as it does not set the local variable(#OutVal).
Instead of exec, use sp_execute_sql with an output parameter:
exec sp_executesql #FinalQuery, N'#OutVal output', #OutVal = #OutVal out
Since sp_executesql expects nvarchar parameters, be sure to change the definition of #FinalQuery to nvarchar(max).
#OutVal in query string does not recognized as a variable. use a function or return table statement.

How to return the rowcount of a dynamic sql query in SP?

I would like to return the rowcount of a dynamic sql query using linq similar to mentioned here:
http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
I'm using dynamic sql to create the where clause and to implement paging on the result set, The rowcount I want to return is the total number of records that meet the where condition.
My SQL that is causing me problems:
-- get row count
SET #SQL = '#TotalRowCount = SELECT COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + ')) '
exec sp_executesql #SQL
END
(I need this to be the output param #TotalRowCount in the param list here):
ALTER PROCEDURE [dbo].[_tournament_GetTournamentsByConveners]
(
#LastName varchar(100) = null ,
#Username varchar(256) = null ,
#Email varchar(100) = null ,
#IsWildcard bit = null,
#PageIndex int ,
#PageSize int,
#TotalRowCount int output
)
AS
This is by design.
The scope of the #TotalRowCount in the dynamic SQL is different to the scope of #TotalRowCount declared in the stored procedure. That is, the dynamic SQL has it's own scope.
If you insist on using dynamic SQL, do this to add the total rows to the record set that is returned
SELECT col1, col2,
COUNT(*) OVER () AS TotalRows
FROM ...
Otherwise we only have partial code to offer any suggestions for improvement. You appear to have LINQ to call stored procs with execute dynamic SQL. This is too convoluted.
You can declare output parameters with sp_executesql. So to get your result alter the code as shown below.
Always be careful when concatenating SQL code like this as it is extremely vulnerable to SQL injection.
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + N')) '
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output
you can also express this more compact as:
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount' + #WHERE + ISNULL(#SUBWHERE, N'') + N'))'
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output