Data type error when passing dynamic column names into SQL Server - sql

I've run into an issue while executing a stored procedure from VBA: I want to pass in a column name as a string for the parameter, and then use a case statement to select the actual column name in the data.
This query works fine when the column name (#FACTOR) i'm passing through is an integer, but not when it's a varchar. I get a conversion error:
Error converting data type nvarchar to float.
Here's my code:
WITH T0 AS (
SELECT DISTINCT
CASE #FACTOR
WHEN 'DRVREC' THEN DRIVINGRECORD --OK
WHEN 'POAGE' THEN POAGE
WHEN 'ANNUALKM' THEN AMC_VH_ANNL_KM
WHEN 'DAILYKM' THEN AMC_VH_KM_TO_WRK
WHEN 'RATETERR' THEN AMC_VH_RATE_TERR --OK
WHEN 'BROKERNAME' THEN MASTERBROKER_NAME
WHEN 'DRVCLASS' THEN DRIVINGCLASS -- OK
WHEN 'VEHAGE' THEN VEH_AGE -- OK
WHEN 'YEARSLIC' THEN YRSLICENSE
WHEN 'COVERAGECODE' THEN COVERAGECODE
ELSE NULL END AS FACTOR FROM DBO.Automation_Data
),
...
...
Or perhaps the example below is more concise:
DECLARE #FACTOR varchar(50)
SELECT #FACTOR = 'NOT_A_VARCHAR'
SELECT CASE #FACTOR
WHEN 'A_VARCHAR' THEN COLUMNNAME1
WHEN 'NOT_A_VARCHAR' THEN COLUMNNAME2
ELSE NULL END AS FACTOR FROM dbo.myTable
^ This would work, but if #FACTOR = 'A_VARCHAR' then i get the error.
Thanks in advance!
UPDATE **********************************:
It appears to be an issue with the case statement itself?
When I only have the varchar option in my case statement, the query runs. When I un-comment the second part of the case statement I get the error.
DECLARE #FACTOR varchar(50)
SELECT #FACTOR = 'A_VARCHAR'
SELECT CASE #FACTOR
WHEN 'A_VARCHAR' THEN COLUMNNAME1
--WHEN 'NOT_A_VARCHAR' THEN COLUMNNAME2 ELSE NULL
END AS FACTOR FROM dbo.myTable

When you are selecting from multiple columns as a single column like you are doing, SQL returns the result as the highest precedence type. Same goes with coalesce etc. when a single result is to be returned from multiple data types.
If you try the code below for example, 3rd select will return the error you're getting, as it tries to convert abc to int (higher precedence). If you set #V to '123', error will go away, as the convert from '123' to int/float works. When you check the 'BaseType' of the result, you can see it shows the highest precedence data type of the mixed types.
DECLARE #F int = 1 --if you use float here error message will show ...'abc' to data type float.
DECLARE #V varchar(5) = 'abc'
DECLARE #O varchar = '1'
SELECT CASE WHEN #O = '1' THEN #F ELSE #V END --no error
SELECT SQL_VARIANT_PROPERTY((SELECT CASE WHEN #O = '1' THEN #F ELSE #V END), 'BaseType') --int/float
SET #O = '2'
SELECT CASE WHEN #O = '1' THEN #F ELSE #V END --error: Conversion failed when converting the varchar value 'abc' to data type int.
When you converted all your selects to nvarchar, nvarchar became the highest precedence data type, so it worked. But if you know some of your columns are float and some of them nvarchar, you only need to convert float columns to nvarchar. So this will work as well:
SET #O = '2'
SELECT CASE WHEN #O = '1' THEN CONVERT(NVARCHAR(5), #F) ELSE #V END
See SQL Data Type Precedence

Related

SQL : Error converting data type nvarchar to float

I want to convert nvarchar data to float type.
In my case I have SalesValue column and I used this command
UPDATE Overseas
SET SalesValue = CONVERT(FLOAT, REPLACE([SalesValue],',','') )
My table has values like
201.01
40.50
215.12
550
304.201
But I get an error
SQL : Error converting data type nvarchar to float.
How can I solve this problem ?
You should find the values that do not match. In SQL Server 2012+, you can use try_convert(). That is not available. So, how about this?
SELECT SalesValue
FROM Overseas
WHERE SalesValue LIKE '%[^0-9,.]%' OR
SalesValue LIKE '%[.,]%[.,]%';
I think that covers the obvious irregularities: a character that is not numeric or two (or more) decimal points.
1st cast value by using below query and then update normally
SELECT
case when ISNUMERIC([SalesValue])=1
then CAST([SalesValue] AS FLOAT) else 0 end AS CastedValue)
FROM your_table_name
It sounds like to me your data still has something that is non numerical. I'm hoping that your application side had done a pretty good job at cleaning your data before inputting and the problem is probably you have a '$' in one or more of your fields.
Cast will fail when you have a non numerical char other then '.' in it (as you probably know which is why you removed ',').
I ran the below script to test this.
declare #myFloat float;
declare #test1 nvarchar(10) = '145.88';
declare #test2 nvarchar(10) = '4,145.88';
declare #test3 nvarchar(10) = '$4,145.88';
SELECT ISNUMERIC(#TEST3)
set #myFloat = CONVERT(FLOAT, REPLACE(#test1,',','') );
select #myFloat;
set #myFloat = CONVERT(FLOAT, REPLACE(#test2,',','') );
select #myFloat;
--THIS WILL FAIL
set #myFloat = CONVERT(FLOAT, REPLACE(#test3,',','') );
select #myFloat;
--THIS WILL NOT FAIL
set #myFloat = CONVERT(FLOAT, REPLACE(REPLACE(#test3,',',''),'$','') );
select #myFloat;
You can try running the below script on the column in question to see which columns you are having a problem with:
--run this on your table
SELECT SalesValue
FROM Overseas
WHERE ISNUMERIC(REPLACE(SalesValue,',','')) = 0
--test sample
/*
insert into #myTable
values ('145.88'),
('4,145.88'),
('$4,145.88'),
('$4,145.88%');
SELECT *
FROM #myTable
WHERE ISNUMERIC(REPLACE(amounts,',','')) = 0
--WHERE ISNUMERIC(REPLACE(REPLACE(amounts,',',''),'$','')) = 0 --this will remove results with $ also
*/
So your fix will be to simply change the line you provided to:
UPDATE Overseas SET SalesValue = CONVERT(FLOAT, REPLACE(REPLACE([SalesValue],',',''),'$','') )
Unless you found other chars in the results of prior script.
This will get you closer than ISNUMERIC()
declare #table table (SalesValue varchar(16))
insert into #table
values
('1e4'),
('$'),
('134.55'),
('66,9897'),
('14')
select
SalesValue
,case
when SalesValue NOT LIKE '%[^0-9,.]%'
then convert(decimal(16,4),replace(SalesValue,',','.'))
end
from
#table

SQL - "incrementing" a char value causes collation error

I'm dealing with a table in which a bunch of arbitrary settings are stored as VARCHAR(255) values. The particular one I'm tasked with dealing with is a sequence number that needs to be incremented and returned to the caller. (Again, note that the sequence "number" is stored as VARCHAR, which is something I don't have any control over).
Because it's a sequence number, I don't really want to select and update in separate steps. When I've dealt with this sort of thing in the past with actual numeric fields, my method has been something like
UPDATE TABLE SET #SEQ_NUM = VALUE = VALUE + 1
which increments the value and gives me the updated value in one swell foop. I thought in this situation, I'd try the same basic thing with casts:
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
The actual update works fine so long as I don't try to assign the result to the variable; as soon as I do, I receive the following error:
Msg 549, Level 16, State 1, Line 4 The collation
'SQL_Latin1_General_CP1_CI_AS' of receiving variable is not equal to
the collation 'Latin1_General_BIN' of column 'VALUE'.
I understand what that means, but I don't understand why it's happening, or by extension, how to remedy the issue.
As an aside to fixing the specific error, I'd welcome suggestions for alternative approaches to incrementing a char sequence "number".
From one of the comments, sounds like you may have already hit on this, but here's what I would recommend:
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE
WHERE NAME = 'SOME_NAME'
This will output the new value like a SELECT statement does. You can also cast inserted.VALUE to an int if you wanted to do that in the SQL.
If you wanted to put the value into #SEQ_NUM instead of outputing the value from the statement/stored procedure, you can't use a scalar variable, but you can pump it into a table variable, like so:
DECLARE #SEQ_NUM AS TABLE ( VALUE VARCHAR(255) );
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE INTO #SEQ_NUM ( VALUE )
WHERE NAME = 'SOME_NAME'
SELECT VALUE FROM #SEQ_NUM
Maintaining a sequential number manually is by no means a solution I'd like to work with, but I can understand there might be constraints around this.
If you break it down in to 2 steps, then you can work around the issue. Note I've replaced your WHERE clause for this example code to work:
CREATE TABLE #SOME_TABLE ( [VALUE] VARCHAR(255) )
INSERT INTO #SOME_TABLE
( VALUE )
VALUES ( '12345' )
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE #SOME_TABLE
SET [VALUE] = CAST(( CAST([VALUE] AS INT) + 1 ) AS VARCHAR(255))
WHERE 1 = 1
SELECT *
FROM #SOME_TABLE
SELECT #SEQ_NUM = [VALUE]
FROM #SOME_TABLE
WHERE 1 = 1
SELECT #SEQ_NUM
DROP TABLE #SOME_TABLE
You can continue using the quirky update in OP but you have to split the triple assignment #Variable = Column = Expression in the UPDATE statement to two simple assignments of #Variable = Expression and Column = #Variable like this
CREATE TABLE #SOME_TABLE (
NAME VARCHAR(255)
, VALUE VARCHAR(255) COLLATE Latin1_General_BIN
)
INSERT #SOME_TABLE SELECT 'SOME_NAME', '42'
DECLARE #SEQ_NUM VARCHAR(255)
/*
-- this quirky update fails on COLLATION mismatch or data-type mismatch
UPDATE #SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
*/
-- this quirky update works in all cases
UPDATE #SOME_TABLE
SET #SEQ_NUM = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
, VALUE = #SEQ_NUM
WHERE NAME = 'SOME_NAME'
SELECT *, #SEQ_NUM FROM #SOME_TABLE
This simple rewrite prevents db-engine complaining on difference in data-type between #Variable and Column too (e.g. VARCHAR vs NVARCHAR) and seems like a more "portable" way of doing quirky updates (if there is such thing)

SQL Server compare varchar field with binary(16)

I have 2 tables - Table A with primary key column of type binary(16) and another table B with foreign key referring to the same column but with data type as varchar(50). So table A has values like 0x0007914BFFEC4603A6900045492EFA1A and table B has the same value stored as 0007914BFFEC4603A6900045492EFA1A.
How do i compare these 2 columns, which would give me
0007914BFFEC4603A6900045492EFA1A = 0x0007914BFFEC4603A6900045492EFA1A
You will need to convert the binary(16) to a string. A sample of how to do this can be found in the question below. This question converts a varbinary to a string, but the same technique can be used for a binary column or variable:
SQL Server converting varbinary to string
Example code for how to do this is below:
declare #bin binary(16), #str varchar(50)
set #bin = 0x0007914BFFEC4603A6900045492EFA1A
set #str = '0007914BFFEC4603A6900045492EFA1A'
select #bin as'binary(16)', #str as 'varchar(50)'
-- the binary value is not equal to the string value
-- this statement returns 'binary value is not equal to string'
if #bin = #str select 'binary value is equal to string'
else select 'binary value is not equal to string'
declare #binstr varchar(50)
select #binstr = convert(varchar(50), #bin, 2)
select #binstr
-- the converted string value matches the other string
-- the result of this statement is "converted string is equal"
if #binstr = #str select 'converted string is equal'
else select 'converted string is NOT equal'
To use this in a join, you can include the conversion in the ON clause of the inner join or in a WHERE clause:
select *
from TableA
inner join TableB
on TableB.char_fk = convert(varchar(50), TableA.bin_pk, 2)
UPDATE
For SQL Server 2005, you can use an XML approach shown by Peter Larsson here:
-- Prepare value
DECLARE #bin VARBINARY(MAX)
SET #bin = 0x5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
-- Display the results
SELECT #bin AS OriginalValue,
CAST('' AS XML).value('xs:hexBinary(sql:variable("#bin"))', 'VARCHAR(MAX)') AS ConvertedString
You can also use the undocumented function sys.fn_varbintohexstr, but as this post on dba.stackexchange.com explains, there are several reasons why you should avoid it.
CONVERT with style 2 to get a binary representation of the hexadecimal string;
... where TableA.bin_pk = CONVERT(VARBINARY, TableB.char_fk, 2)
The correct aproach is to set both fields in the same datatype. in order to to do this create a new table say temp and use select into and convert:
select field1,...,convert(varchar(50),varbinary(16),fieldToConvert)...,fieldN
into myNewTable
Found the answer. I need to use
master.dbo.fn_varbintohexstr (#source)
which converts a varbinary to varchar, and then works perfectly well for comparison in my scenario.

casting to Integer is not working properly?

execute these and check the result why is it so ?
declare #a decimal(8,3) =235.363
declare #b int =1
select case #b
when 1 then cast(#a as int)
when 2 then CAST(#a as decimal(8,3))
end
Result : 235.000
declare #a decimal(8,3) =235.363
declare #b int =1
select case #b
when 1 then cast(#a as int)
--when 2 then CAST(#a as decimal(8,3))
end
Result : 235
declare #a decimal(8,3) =235.363
declare #b int =1
select case #b
when 1 then cast(#a as tinyint)
when 2 then CAST(#a as float)
end
Result : 235
What you see is not what you get.
For the column type, SQL Server picks the correct, more wide type (float over tinyint, decimal over int). You can verify that by doing select into instead of just select.
It's just the display rules that are different.
When the selected column type is float, you don't see the trailing .000 when there is no fractional part.
For decimal with explicit positions set, such as decimal(8,3), you will see the trailing .000 even if there's no fractional part. If you remove the specifier and only leave decimal as the column type, the .000 will disappear.
All that does not affect the actual column type, which is always the widest one.
This behaviour is documented in the BOL entry for CASE
Return Types
Returns the highest precedence type from the set of types in
result_expressions and the optional else_result_expression. For more
information, see Data Type Precedence (Transact-SQL).
If you follow the link to data type precedence you will see that float has higher precedence than decimal which in turn has higher precedence than tinyint so this behaviour is expected.
Probably cast operation will cast all the options to a bigger type.
From MSDN:
The data types of input_expression and each when_expression must be
the same or must be an implicit conversion.
http://msdn.microsoft.com/en-us/library/ms181765.aspx
Casting to Integer is not working properly.
Your statement is not correct!
In CASE statement, you can only return one type of data, so according to your statement you can return either INT or decimal(8,3), since your case statement has decimal(8,3) so here INT data is implicitly converted to decimal! Please see below example:, always try to use same return type in CASE statement to get proper and expected result, thanks.
1.
select case #b
when 1 then CAST(#a as int) -- return type INT
when 2 then CAST(#a as int) -- return type INT
end
2.
select case #b
when 1 then CAST(#a as int) -- return type INT and then converted to decimal(8,3)
when 2 then CAST(#a as decimal(8,3)) -- return type return type INT
end

SQL for nvarchar 0 = '' & = 0?

I was searching for integers in a nvarchar column. I noticed that if the row contains '' or 0 it is picked up if I search using just 0.
I'm assuming there is some implicit conversion happening which is saying that 0 is equal to ''. Why does it assign two values?
Here is a test:
--0 Test
create table #0Test (Test nvarchar(20))
GO
insert INTO #0Test (Test)
SELECT ''
UNION ALL
SELECT 0
UNION ALL
SELECT ''
Select *
from #0Test
Select *
from #0Test
Where test = 0
SELECT *
from #0Test
Where test = '0'
SELECT *
from #0Test
Where test = ''
drop table #0Test
The behavior you see is the one describe din the product documentation. The rules of Data Type Precedence specify that int has higher precedence than nvarchar therefore the operation has to occur as an int type:
When an operator combines two expressions of different data types, the
rules for data type precedence specify that the data type with the
lower precedence is converted to the data type with the higher
precedence
Therefore your query is actually as follow:
Select *
from #0Test
Where cast(test as int) = 0;
and the empty string N'' yields the value 0 when cast to int:
select cast(N'' as int)
-----------
0
(1 row(s) affected)
Therefore the expected result is the one you see, the rows with an empty string qualify for the predicate test = 0. Further proof that you should never mix types freely. For a more detailed discussion of the topic, see How Data Access Code Affects Database Performance.
You are implicitly converting the field to int with your UNION statement.
Two empty strings and the integer 0 will result in an int field. This is BEFORE you insert into the nvarchar field, so the data type in the temp table is irrelevant.
Try changing the second select in the UNION to:
SELECT '0'
And you will get the expected result.