Passing Int to dynamic stored procedure fails - sql-server

I have a dynamic stored procedure in SQL Server that works well to pivot a table:
CREATE PROCEDURE dbo.DynamicPivotTableInSql
#ColumnToPivot NVARCHAR(255),
#ListToPivot NVARCHAR(255),
#SurveyID INT=10
AS
BEGIN
DECLARE #SqlStatement NVARCHAR(MAX)
SET #SqlStatement = N'SELECT *
FROM
(SELECT
[resp_id], [benefit], [weight]
FROM Segment_Responses) myResults
PIVOT
(SUM([weight])
FOR [' + #ColumnToPivot + ']
IN (' + #ListToPivot + ')) AS PivotTable';
EXEC (#SqlStatement)
END
and I call it like this
EXEC DynamicPivotTableInSql
#ColumnToPivot = 'benefit',
#ListToPivot = '[OBSERVABILITY], [COST], [EASE OF USE], [SERVICE]'
Here is where I run into problems. You'll notice I have hardcoded #SurveyID = 10 and if I try to add that as a where statement in the stored procedure like this:
FROM Segment_Responses
WHERE survey_id = ' + #SurveyID + '
and run the stored procedure again I get this error:
Conversion failed when converting the nvarchar value '
SELECT * FROM (
SELECT
[resp_id],
[benefit],
[weight]
FROM Segment_Responses where survey_id=' to data type int.
I've tried to solve this many ways (e.g., passed the Int variable instead of hard coding it) but always get the same result. Any ideas what is going on?

Just to try to add some clarity, when you add together two different types, SQL Server will (where it can) implicitly convert one to the other - the result must be a single type after all.
It decides which one to convert "to the other" based on an order of precedence.
So where you are trying to concatenate a varchar with an int, the int has the higher order of precedence. This is also a common cause of errors and bugs when using a case expression when mixing types in different execution paths of the expression.
You need to be explicit and cast the int to a varchar.
Ideally you would use a parameterised query which would also reuse the cached execution plan - this may be beneficial if the cardinality of the data is similar but sometimes making the value part of the query dynamically can be advantagous, it depends on the use-case.

This is why the syntax EXEC (#SQL) is strongly suggested against. Use sys.sp_executesql and parametrise your statement:
SET #SQL = N'SELECT ...
FROM ...
WHERE survey_id = #SurveyID ...;';
EXEC sys.sp_executesql #SQL, N'#SurveyID int',SurveyID;

The + only works with strings. If you use a number TSQL assumes you are trying to use the addition operator, and tries to convert the string argument to int.
eg this
select 1 + '2'
works and returns 3.
Use CONCAT instead of +, or use an explicit conversion on the int.
eg
WHERE survey_id = ' + cast(#SurveyID as varchar(20)) + '

Related

Cannot pass string to IN clause in stored procedure

I have the following stored procedure that takes one parameter and I need to use that in my IN clause, but that does not work. I get this error when trying.
Conversion failed when converting the varchar value '1,2' to data type int
Here is my stored procedure..
CREATE PROCEDURE [dbo].[p_GetSegment]
#SegmentIds nVarChar(20)
AS
BEGIN
SET NOCOUNT ON;
SELECT dbo.Segment.Name
FROM dbo.tbl_Category
INNER JOIN dbo.Segment ON dbo.tbl_Category.SegmentId
WHERE (dbo.Segment.Id IN (#SegmentIds))
I pass in "1,2". How can I make this work?
In SQL Server 2016+, you can use string_split():
WHERE dbo.Segment.Id IN (SELECT value FROM STRING_SPLIT(#SegmentIds, ','))
You can use any string split function for this purpose. You can also just use `like:
where ',' + #SegmentIds + ',' like '%,' + convert(varchar(255), dbo.Segment.Id) + ',%'

Updating table using dynamic query

SET #TempTable = 'UPDATE #TempTable SET AvgVal = ' + #AvgVal + '
WHERE PropertyName =''' + #PropertyName + '''' + ' AND CrudeName =''' + #crudeName + ''''
I have an update query which is dynamic. AvgVal is float. The variable has the same datatype in the table as well. Still I get an error saying -
Error converting data type varchar to numeric.
If i convert the value to varchar it works. Whats the workaround for this.
What you call the "workaround" is the answer. SQL Server uses the + operator as both addition and string concatenation. However, because the addition operator has a slightly higher precedence than the concatenation operator, the addition operator is assumed to apply first.
See the SQL Server BOL on Operator Precedence. At the 3rd level of precedence, the order goes:
+ (Positive)
- (Negative)
+ (Add)
+ (Concatenate)
- (Subtract)
& (Bitwise AND),
^ (Bitwise Exclusive OR)
| (Bitwise OR)
To see how this affects queries take the following example:
select '1' + 2
The result of this query is not an error. SQL Server interprets the + as addition. It sees the number 2 is an integer, so it tries to convert the string '1' into an integer and is successful. The result is the integer 3.
However, try this:
select '1.0' + 2
SQL Server again sees this as an addition, notes that 2 is an integer, and tries to convert the string '1.0' into an integer. This conversion fails with the following error:
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting
the varchar value '1.0' to data type int.
In your example, SQL Server detects the float #AvgVal and attempts to convert the string 'UPDATE #TempTable SET AvgVal = ' into a float. The conversion fails spectacularly, and the error you reported occurs.
One possibility is to convert the #AvgVal into a string first:
SET #TempTable = 'UPDATE #TempTable
SET AvgVal = ' + CAST(#AvgVal AS VARCHAR) + '
WHERE PropertyName =''' + #PropertyName + '''' + '
AND CrudeName =''' + #crudeName + '''
Note: This query is vulnerable to a SQL injection attack! To avoid a SQL injection attack, always pass data to a dynamic query as parameters:
SET #TempTable = N'UPDATE #TempTable SET AvgVal = #AvgVal
WHERE PropertyName = #PropertyName AND CrudeName = #crudeName'
exec sp_executesql #TempTable,
N'#AvgVal float, #PropertyName varchar(255), #crudeName varchar(255)',
#PropertyName = #PropertyName,
#CrudeName = #crudeName
This both avoids a SQL injection attack and avoids the conversion error.
I see. Sorry, misunderstood.
Yes, because of the way SQL Server works. You are building a concatenated string. You will have to do the CAST. Type conversions in SQL Server are a little wonky. You would think it would inherently cast to the simplest data type, varchar, but in this instance it isn't. So you will have to do ... + CAST(#AvgVal AS VARCHAR(255)) + ...
Keep in mind some times variables like #PropertyName if is Nvarchar type you should do
in front of the string N'
DECLARE #TempTable NVARCHAR
SET #TempTable = N'UPDATE and so on for the rest

TSQL Passing a varchar variable of column name to SUM function

I need to write a procedure where I have to sum an unknown column name.
The only information I have access to is the column position.
I am able to get the column name using the following:
SELECT #colName = (SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='TABLENAME' AND ORDINAL_POSITION=#colNum)
I then have the following:
SELECT #sum = (SELECT SUM(#colName) FROM TABLENAME)
I then receive the following error:
Operand data type varchar is invalid for sum operator
I am confused about how to make this work. I have seen many posts on convert and cast, but I cannot cast to an float, numeric, etc because this is a name.
I would appreciate any help.
Thank you
This is not possible. You will have to assemble the SQL query and then execute it. Something like this:
SET #sqlquery = 'SELECT SUM(' + #colName + ') FROM TABLENAME'
EXECUTE ( #sqlquery )
Adjust accordingly.

Microsoft sql 2008 string formatting

I wonder if there is something in Microsoft SQL 2008, which can do similar thing in C#
for(int i = 0; i < 10; ++i)
String.Format("ABC {0} XYZ", i.ToString());
Basically, is the String.Format() possible in MS SQL 2008?
Just make it more clear. I want to create a table dynamicaly, so need to generate string for the columns, like
"Col1 FLOAT, Col2 FLOAT. Col3 FLOAT"
The number of column is specified by a variable, so I wonder if there is a function to do String.Format(), so I can use in a loop.
Idealy, I want to write a stored procedure, and pass in
"Col{0} Float"
Declare #variable INT
Declare #columnName varchar(50)
Declare #columnType varchar(10)
Select #columnName + CONVERT(varchar(10), #variable) + ' ' + #columnType
here u go. assign variable to your int value and run in loop or you can create that loop in sql. i would do that in sql instead of going back and forth. but it depends on your logic.
You are just concatenating text to a column so you can make use of +:
SELECT "Employee Name" + EmployeeName AS EmployeeName FROM MyTable
You do have to watch out with using + though depending on the datatype, but there is always CAST and CONVERT to help you cast datatypes to a specific type.
Next version has a FORMAT function or you can roll your own CLR function in SQL Server 2008 that calls the .NET framework formatting methods but for your stated use case I would just use something like the below.
DECLARE #ColList varchar(max) =
(SELECT ',Col',number+0,' FLOAT'
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 10
FOR XML PATH(''))
SELECT STUFF(#ColList, 1, 1, '')

Help me writing this query

CREATE PROCEDURE [dbo].[sp_SelectRecipientsList4Test] --'6DBF9A01-C88F-414D-8DD9-696749258CEF','Emirates.Description','0','5'
--'6DBF9A01-C88F-414D-8DD9-696749258CEF',
--'121f8b91-a441-4fbf-8a4f-563f53fcc103'
(
#p_CreatedBy UNIQUEIDENTIFIER,
#p_SortExpression NVARCHAR(100),
#p_StartIndex INT,
#p_MaxRows INT
)
AS
SET NOCOUNT ON;
IF LEN(#p_SortExpression) = 0
SET #p_SortExpression = 'Users.Name Asc'
DECLARE #sql NVARCHAR(4000)
SET #sql='
DECLARE #p_CreatedBy UNIQUEIDENTIFIER
SELECT
Name,
POBox,
EmirateName,
TelephoneNo,
RecipientID,
CreatedBy,
CreatedDate,
ID
FROM
(
SELECT Users.Name, Users.POBox, Emirates.Description As EmirateName,
UserDetails.TelephoneNo, AddressBook.RecipientID,AddressBook.CreatedBy, AddressBook.CreatedDate,
AddressBook.ID,
ROW_NUMBER() OVER(ORDER BY '+ #p_SortExpression +') AS Indexing
FROM AddressBook INNER JOIN
Users ON AddressBook.RecipientID = Users.ID INNER JOIN
UserDetails ON Users.ID = UserDetails.UserID INNER JOIN
Emirates ON Users.EmiratesID = Emirates.ID
----WHERE (AddressBook.CreatedBy = #p_CreatedBy)
) AS NewDataTable
WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) + ' + '
+ CONVERT(NVARCHAR(10),#p_MaxRows)+') '
EXEC sp_executesql #sql
This query is not giving any error but also not giving any result
please help
Have you tried breaking down the statement, to check if intermediate results are as expected? That's what you do to debug a complex statement...
For example, there's a nested SELECT in there. If you commit that SELECT on its own, does it print the expected values?
Edit: There's a saying about teaching a man to fish. 'ck' and 'n8wrl' have given you fish to eat today, now please practice fishing to feed you tomorrow...
Well, a quick glance of this:
WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) + ' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) +...
looks like you're looking for an impossible condition, not unlike this:
WHERE Indexing > 5 AND Indexing <= 5
So that might be why you're getting no rows, but this proc is ripe for SQL injection attacks too. Building SQL on the fly based on possibly-unvalidated parameters is very dangerous.
You are querying:
'WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing<=(' + CONVERT (NVARCHAR(10),#p_StartIndex ) + ' + '
and then adding max rows as a string, you can do this much more easily like so:
'WHERE Indexing > '+ CONVERT(NVARCHAR(10), #p_StartIndex) +
' AND Indexing <='+ CONVERT(NVARCHAR(10),#p_StartIndex + #p_MaxRows)
EDIT
The problem with your inner WHERE is that you are passing in the parameter, you need to do
'WHERE (AddressBook.CreatedBy = ''' + CAST(#p_CreatedBy AS CHAR(36)) + ''')'
Are you sure all your joins should be inner joins?
Change sp_executesql to PRINT and see what gets generated (the poor man's debugger)
Besides what all the other people told you,
give me one good reason why you are using sp_executesql over exec? You are not using parameterized statements, you also are not protected from sql injections because you just execute the whole string
This will just bloat the procedure cache everytime this is run and some values change, you will get a new plan every time
Please take a look at Changing exec to sp_executesql doesn't provide any benefit if you are not using parameters correctly and Avoid Conversions In Execution Plans By Using sp_executesql Instead of Exec

Resources