Why does SQL Server require INT to be converted to NVARCHAR? - sql-server

During an ordeal yesterday, I learned that you can't pass this query to EXEC():
#SQL = #SQL + 'WHERE ID = ' + #SomeID
EXCEC(#SQL)
Where #SomeID is an INT and #SQL is NVARCHAR. This will complain about not being able to convert NVARCHAR back to INT during execution.
I realized you have to do it like
#SQL = #SQL + 'WHERE ID = ' + CONVERT(NVARCHAR(20), #SomeID)
What I didn't understand is why? Why doesn't SQL Server understand the INT when simply +:ed on to an NVARCHAR? I'm guessing it has something to do with char sets.
EDIT: Fixed typos (missed some +:s).

+ (String Concatenation)
An operator in a string expression
that concatenates two or more
character or binary strings, columns,
or a combination of strings and column
names into one expression (a string
operator).
Expression is any valid Microsoft® SQL Server™
expression of any of the data types in
the character and binary data type
category, except the image, ntext, or
text data types. Both expressions must
be of the same data type, or one
expression must be able to be
implicitly converted to the data type
of the other expression.
So... There is no implicit convertion of int to string... This is an internal question

I'm not saying this will definitely work, and I'm not near my sql management studio to try it before posting, but have you tried something like this:
#SQL = #SQL + 'Where ID = ' + #SomeID
EXEC(#SQL)

Related

Passing Int to dynamic stored procedure fails

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)) + '

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, '')

SQL Server expression substitution in SELECT statements as column names

How do I evaluate a character expression to resolve to a valid column name in a SELECT statement that would return column row values? Eg valid column name = Customer_1 == 'Customer_'+'1'
You need to use dynamic SQL. An example
DECLARE #DynSQL nvarchar(max)
DECLARE #Suffix int = 1
SET #DynSQL = N'SELECT Customer_' + CAST(#Suffix as nvarchar(10)) +
N' FROM YourTable WHERE foo = #foo'
EXEC sp_executesql #DynSQL, N'#foo int', #foo=1
As always with dynamic SQL you need to consider SQL injection if any of the inputs to the process will be user supplied.
How do I evaluate a character expression to resolve to a valid column name in a SELECT statement that would return column row values? Eg valid column name = Customer_1 == 'Customer_'+'1'
You're probably doing something wrong if you need to do this, but if you have to: build the column names as rows and then pivot.

Resources