CAST and IsNumeric - sql-server
Why would the following query return "Error converting data type varchar to bigint"? Doesn't IsNumeric make the CAST safe? I've tried every numeric datatype in the cast and get the same "Error converting..." error. I don't believe the size of the resulting number is a problem because overflow is a different error.
The interesting thing is, in management studio, the results actually show up in the results pane for a split second before the error comes back.
SELECT CAST(myVarcharColumn AS bigint)
FROM myTable
WHERE IsNumeric(myVarcharColumn) = 1 AND myVarcharColumn IS NOT NULL
GROUP BY myVarcharColumn
Any thoughts?
IsNumeric returns 1 if the varchar value can be converted to ANY number type. This includes int, bigint, decimal, numeric, real & float.
Scientific notation could be causing you a problem. For example:
Declare #Temp Table(Data VarChar(20))
Insert Into #Temp Values(NULL)
Insert Into #Temp Values('1')
Insert Into #Temp Values('1e4')
Insert Into #Temp Values('Not a number')
Select Cast(Data as bigint)
From #Temp
Where IsNumeric(Data) = 1 And Data Is Not NULL
There is a trick you can use with IsNumeric so that it returns 0 for numbers with scientific notation. You can apply a similar trick to prevent decimal values.
IsNumeric(YourColumn + 'e0')
IsNumeric(YourColumn + '.0e0')
Try it out.
SELECT CAST(myVarcharColumn AS bigint)
FROM myTable
WHERE IsNumeric(myVarcharColumn + '.0e0') = 1 AND myVarcharColumn IS NOT NULL
GROUP BY myVarcharColumn
Background:
I use a 3rd Party database which constantly recieves new data from other 3rd party vendors.
It's my job to parse out a horrendous varchar field used to store results.
We want to parse out as much data as possible, and this solution shows you how you can "clean up" the data so that valid entries do not get overlooked.
Some results are free-texted.
Some are Enumerations (Yes, No, Blue, Black, etc..).
Some are Integers.
Others use decimals.
Many are percentages, which if converted to an integer could trip you up later.
If I need to query for a given decimal range (say -1.4 to 3.6 where applicable) my options are limited.
I updated my query below to use #GMastros suggestion to append 'e0'.
Thanks #GMastros, this saved me an extra 2 lines of logic.
Solution:
--NOTE: I'd recommend you use this to convert your numbers and store them in a separate table (or field).
-- This way you may reuse them when when working with legacy/3rd-party systems, instead of running these calculations on the fly each time.
SELECT Result.Type, Result.Value, Parsed.CleanValue, Converted.Number[Number - Decimal(38,4)],
(CASE WHEN Result.Value IN ('0', '1', 'True', 'False') THEN CAST(Result.Value as Bit) ELSE NULL END)[Bit],--Cannot convert 1.0 to Bit, it must be in Integer format already.
(CASE WHEN Converted.Number BETWEEN 0 AND 255 THEN CAST(Converted.Number as TinyInt) ELSE NULL END)[TinyInt],
(CASE WHEN Converted.Number BETWEEN -32768 AND 32767 AND Result.Value LIKE '%\%%' ESCAPE '\' THEN CAST(Converted.Number / 100.0 as Decimal(9,4)) ELSE NULL END)[Percent],
(CASE WHEN Converted.Number BETWEEN -32768 AND 32767 THEN CAST(Converted.Number as SmallInt) ELSE NULL END)[SmallInt],
(CASE WHEN Converted.Number BETWEEN -214748.3648 AND 214748.3647 THEN CAST(Converted.Number as SmallMoney) ELSE NULL END)[SmallMoney],
(CASE WHEN Converted.Number BETWEEN -2147483648 AND 2147483647 THEN CAST(Converted.Number as Int) ELSE NULL END)[Int],
(CASE WHEN Converted.Number BETWEEN -2147483648 AND 2147483647 THEN CAST(CAST(Converted.Number as Decimal(10)) as Int) ELSE NULL END)[RoundInt],--Round Up or Down instead of Truncate.
(CASE WHEN Converted.Number BETWEEN -922337203685477.5808 AND 922337203685477.5807 THEN CAST(Converted.Number as Money) ELSE NULL END)[Money],
(CASE WHEN Converted.Number BETWEEN -9223372036854775808 AND 9223372036854775807 THEN CAST(Converted.Number as BigInt) ELSE NULL END)[BigInt],
(CASE WHEN Parsed.CleanValue IN ('1', 'True', 'Yes', 'Y', 'Positive', 'Normal') THEN CAST(1 as Bit)
WHEN Parsed.CleanValue IN ('0', 'False', 'No', 'N', 'Negative', 'Abnormal') THEN CAST(0 as Bit) ELSE NULL END)[Enum],
--I couln't use just Parsed.CleanValue LIKE '%e%' here because that would match on "True" and "Negative", so I also had to match on only allowable characters. - 02/13/2014 - MCR.
(CASE WHEN ISNUMERIC(Parsed.CleanValue) = 1 AND Parsed.CleanValue LIKE '%e%' THEN Parsed.CleanValue ELSE NULL END)[Exponent]
FROM
(
VALUES ('Null', NULL), ('EmptyString', ''), ('Spaces', ' - 2 . 8 % '),--Tabs and spaces mess up IsNumeric().
('Bit', '0'), ('TinyInt', '123'), ('Int', '123456789'), ('BigInt', '1234567890123456'),
--('VeryLong', '12345678901234567890.1234567890'),
('VeryBig', '-1234567890123456789012345678901234.5678'),
('TooBig', '-12345678901234567890123456789012345678.'),--34 (38-4) is the Longest length of an Integer supported by this query.
('VeryLong', '-1.2345678901234567890123456789012345678'),
('TooLong', '-12345678901234567890.1234567890123456789'),--38 Digits is the Longest length of a Number supported by the Decimal data type.
('VeryLong', '000000000000000000000000000000000000001.0000000000000000000000000000000000000'),--Works because Casting ignores leading zeroes.
('TooLong', '.000000000000000000000000000000000000000'),--Exceeds the 38 Digit limit for all Decimal types after the decimal-point.
--Dot(.), Plus(+), Minus(-), Comma(,), DollarSign($), BackSlash(\), Tab(0x09), and Letter-E(e) all yeild false-posotives with IsNumeric().
('Decimal', '.'), ('Decimal', '.0'), ('Decimal', '3.99'),
('Positive', '+'), ('Positive', '+20'),
('Negative', '-'), ('Negative', '-45'), ('Negative', '- 1.23'),
('Comma', ','), ('Comma', '1,000'),
('Money', '$'), ('Money', '$10'),
('Percent', '%'), ('Percent', '110%'),--IsNumeric will kick out Percent(%) signs.
('BkSlash', '\'), ('Tab', CHAR(0x09)),--I've actually seen tab characters in our data.
('Exponent', 'e0'), ('Exponent', '100e-999'),--No SQL-Server datatype could hold this number, though it is real.
('Enum', 'True'), ('Enum', 'Negative')
) AS Result(Type, Value)--O is for Observation.
CROSS APPLY
( --This Step is Optional. If you have Very Long numbers with tons of leading zeros, then this is useful. Otherwise is overkill if all the numbers you want have 38 or less digits.
--Casting of trailing zeros count towards the max 38 digits Decimal can handle, yet Cast ignores leading-zeros. This also cleans up leading/trailing spaces. - 02/25/2014 - MCR.
SELECT LTRIM(RTRIM(SUBSTRING(Result.Value, PATINDEX('%[^0]%', Result.Value + '.'), LEN(Result.Value))))[Value]
) AS Trimmed
CROSS APPLY
(
SELECT --You will need to filter out other Non-Keyboard ASCII characters (before Space(0x20) and after Lower-Case-z(0x7A)) if you still want them to be Cast as Numbers. - 02/15/2014 - MCR.
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(Trimmed.Value,--LTRIM(RTRIM(Result.Value)),
(CHAR(0x0D) + CHAR(0x0A)), ''),--Believe it or not, we have people that press carriage return after entering in the value.
CHAR(0x09), ''),--Apparently, as people tab through controls on a page, some of them inadvertently entered Tab's for values.
' ', ''),--By replacing spaces for values (like '- 2' to work), you open the door to values like '00 12 3' - your choice.
'$', ''), ',', ''), '+', ''), '%', ''), '/', '')[CleanValue]
) AS Parsed--P is for Parsed.
CROSS APPLY
( --NOTE: I do not like my Cross-Applies to feed into each other.
-- I'm paranoid it might affect performance, but you may move this into the select above if you like. - 02/13/2014 - MCR.
SELECT (CASE WHEN ISNUMERIC(Parsed.CleanValue + 'e0') = 1--By concatenating 'e0', I do not need to check for: Parsed.CleanValue NOT LIKE '%e%' AND Parsed.CleanValue NOT IN ('.', '-')
-- If you never plan to work with big numbers, then could use Decimal(19,4) would be best as it only uses 9 storage bytes compared to the 17 bytes that 38 precision requires.
-- This might help with performance, especially when converting a lot of data.
AND CHARINDEX('.', REPLACE(Parsed.CleanValue, '-', '')) - 1 <= (38-4)--This is the Longest Integer supported by Decimal(38,4)).
AND LEN(REPLACE(REPLACE(Parsed.CleanValue, '-', ''), '.', '')) <= 38--When casting to a Decimal (of any Precision) you cannot exceed 38 Digits. - 02/13/2014 - MCR.
THEN CAST(Parsed.CleanValue as Decimal(38,4))--Scale of 4 used is the max that Money has. This is the biggest number SQL Server can hold.
ELSE NULL END)[Number]
) AS Converted--C is for Converted.
Output:
The screenshot below was formatted and cut down to fit on StackOverflow.
The actual results have more columns.
Research:
Next to each query is the result.
It's interesting to see IsNumeric's shortcomings as well as CASTing's limitations.
I show this so you may see the background research that went into writing the query above.
It's important to understand each design decision (in case you're thinking of cutting anything out).
SELECT ISNUMERIC('')--0. This is understandable, but your logic may want to default these to zero.
SELECT ISNUMERIC(' ')--0. This is understandable, but your logic may want to default these to zero.
SELECT ISNUMERIC('%')--0.
SELECT ISNUMERIC('1%')--0.
SELECT ISNUMERIC('e')--0.
SELECT ISNUMERIC(' ')--1. --Tab.
SELECT ISNUMERIC(CHAR(0x09))--1. --Tab.
SELECT ISNUMERIC(',')--1.
SELECT ISNUMERIC('.')--1.
SELECT ISNUMERIC('-')--1.
SELECT ISNUMERIC('+')--1.
SELECT ISNUMERIC('$')--1.
SELECT ISNUMERIC('\')--1. '
SELECT ISNUMERIC('e0')--1.
SELECT ISNUMERIC('100e-999')--1. No SQL-Server datatype could hold this number, though it is real.
SELECT ISNUMERIC('3000000000')--1. This is bigger than what an Int could hold, so code for these too.
SELECT ISNUMERIC('1234567890123456789012345678901234567890')--1. Note: This is larger than what the biggest Decimal(38) can hold.
SELECT ISNUMERIC('- 1')--1.
SELECT ISNUMERIC(' 1 ')--1.
SELECT ISNUMERIC('True')--0.
SELECT ISNUMERIC('1/2')--0. No love for fractions.
SELECT CAST('e0' as Int)--0. Surpise! Casting to Decimal errors, but for Int is gives us zero, which is wrong.
SELECT CAST('0e0' as Int)--0. Surpise! Casting to Decimal errors, but for Int is gives us zero, which is wrong.
SELECT CAST(CHAR(0x09) as Decimal(12,2))--Error converting data type varchar to numeric. --Tab.
SELECT CAST(' 1' as Decimal(12,2))--Error converting data type varchar to numeric. --Tab.
SELECT CAST(REPLACE(' 1', CHAR(0x09), '') as Decimal(12,2))--Error converting data type varchar to numeric. --Tab.
SELECT CAST('' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('' as Int)--0. Surpise! Casting to Decimal errors, but for Int is gives us zero, which is wrong.
SELECT CAST(',' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('.' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('-' as Decimal(12,2))--Arithmetic overflow error converting varchar to data type numeric.
SELECT CAST('+' as Decimal(12,2))--Arithmetic overflow error converting varchar to data type numeric.
SELECT CAST('$' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('$1' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('1,000' as Decimal(12,2))--Error converting data type varchar to numeric.
SELECT CAST('- 1' as Decimal(12,2))--Error converting data type varchar to numeric. (Due to spaces).
SELECT CAST(' 1 ' as Decimal(12,2))--1.00 Leading and trailing spaces are okay.
SELECT CAST('1.' as Decimal(12,2))--1.00
SELECT CAST('.1' as Decimal(12,2))--0.10
SELECT CAST('-1' as Decimal(12,2))--1.00
SELECT CAST('+1' as Decimal(12,2))--1.00
SELECT CAST('True' as Bit)--1
SELECT CAST('False' as Bit)--0
--Proof: The Casting to Decimal cannot exceed 38 Digits, even if the precision is well below 38.
SELECT CAST('1234.5678901234567890123456789012345678' as Decimal(8,4))--1234.5679
SELECT CAST('1234.56789012345678901234567890123456789' as Decimal(8,4))--Arithmetic overflow error converting varchar to data type numeric.
--Proof: Casting of trailing zeros count towards the max 38 digits Decimal can handle, yet it ignores leading-zeros.
SELECT CAST('.00000000000000000000000000000000000000' as Decimal(8,4))--0.0000 --38 Digits after the decimal point.
SELECT CAST('000.00000000000000000000000000000000000000' as Decimal(8,4))--0.0000 --38 Digits after the decimal point and 3 zeros before the decimal point.
SELECT CAST('.000000000000000000000000000000000000000' as Decimal(8,4))--Arithmetic overflow error converting varchar to data type numeric. --39 Digits after the decimal point.
SELECT CAST('1.00000000000000000000000000000000000000' as Decimal(8,4))--Arithmetic overflow error converting varchar to data type numeric. --38 Digits after the decimal point and 1 non-zero before the decimal point.
SELECT CAST('000000000000000000000000000000000000001.0000000000000000000000000000000000000' as Decimal(8,4))--1.0000
--Caveats: When casting to an Integer:
SELECT CAST('3.0' as Int)--Conversion failed when converting the varchar value '3.0' to data type int.
--NOTE: When converting from character data to Int, you may want to do a double-conversion like so (if you want to Round your results first):
SELECT CAST(CAST('3.5' as Decimal(10)) as Int)--4. Decimal(10) has no decimal precision, so it rounds it to 4 for us BEFORE converting to an Int.
SELECT CAST(CAST('3.5' as Decimal(11,1)) as Int)--3. Decimal (11,1) HAS decimal precision, so it stays 3.5 before converting to an Int, which then truncates it.
--These are the best ways to go if you simply want to Truncate or Round.
SELECT CAST(CAST('3.99' as Decimal(10)) as Int)--3. Good Example of Rounding.
SELECT CAST(FLOOR('3.99') as Int)--3. Good Example fo Truncating.
The best solution would be to stop storing integers in a varchar column. Clearly there is a data issue where the data is interpretable as a numeric but cannot be cast as such. You need to find the record(s) that is(are) the problem and fix them if the data is such that it can and should be fixed. Depending on what you are storing and why it is a varchar to begin with, you may need to fix the query instead of the data. But that will be easier to do also if you first find the records which are blowing up your current query.
How to do that is the issue. It is relatively easy to search for a decimal place in the data to see if you have decimals (other than.0 which would convert) using charindex. You could also look for any record containing e or $ or any other character that could be interpeted as numeric according to the sources already given. If you don't have a lot of records a quick visual scan of the data will probably find it, especially if you sort on that field first.
Sometimes when I've been stuck on finding the bad data that is blowing up a query, I've put the data into a temp table and then tried processing in batches (using interpolation) until I find the one it blows up on. Start with the first 1000 (don't forget to use order by or you won't get the same results when you delete the good records and 1000 is only a best guess if you have millions of records start with a larger number). If it passes, delete those 1000 records and select the next batch. Once it fails, select a smaller batch. Once you are down to a number that can easily be visually scanned, you will find the problem. I've been able to find problem records fairly quickly when I have millions of records and a wierd error that none of the queries I've tried (which are basically guesses as to what might be wrong) have found the issue.
Try this and see if you still get an error...
SELECT CAST(CASE
WHEN IsNumeric(myVarcharColumn) = 0
THEN 0
ELSE myVarcharColumn
END AS BIGINT)
FROM myTable
WHERE IsNumeric(myVarcharColumn) = 1
AND myVarcharColumn IS NOT NULL
GROUP BY myVarcharColumn
ISNUMERIC is just... stupid. You shouln'd use it at all.
All cases bellow return 1:
ISNUMERIC('-')
ISNUMERIC('.')
ISNUMERIC('-$.')
For any integer types instead using: ISNUMERIC(#Value) = 1
just use: (#Value NOT LIKE '[^0-9]') OR (#Value NOT LIKE '-[^0-9]'
The only good solution is not to use ISNUMERIC.
Try wrapping it in a case:
select CASE WHEN IsNumeric(mycolumn) = 1 THEN CAST(mycolumn as bigint) END
FROM stack_table
WHERE IsNumeric(mycolumn) = 1
GROUP BY mycolumn
According to BOL ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0.
Valid numeric data types include the following:
int
numeric
bigint
money
smallint
smallmoney
tinyint
float
decimal
real
So as others pointed out you will have some data that will pass ISNUMERIC test but fail on casting to bigint
I had the same Issue and I came up with the Scalar Function as Im on 2008 SQL
ALTER Function [dbo].[IsInteger](#Value VarChar(18))
Returns Bit
As
Begin
Return IsNull(
(Select Case When CharIndex('.', #Value) > 0
Then 0
Else 1
End
Where IsNumeric(#Value + 'e0') = 1), 0)
End
If you are on 2012 you could use TRY_CONVERT
I had the same issue in MSSQL 2014 triggered by a comma instead of full stop:
isnumeric('9090,23') gives 1;
cast('9090,23' as float) fails
I've replaced ',' with '.'
there are DAX functions (IsError or IfError) that could help in this situation but we don't have those on our SQL Server 2008 R2. Looks like some additional analysis package for SQL Server.
I came across this blog post that might help. I've not run into this issue before and not sure if it'll help you in this instance:
http://dotmad.blogspot.com/2007/02/cannot-call-methods-on-bigint-error.html
Related
Different aggregate functions depending on datatype
I have a T-SQL script that returns all columns in a table, along with datatype and max value MAX(DATALENGTH)) fetching it from sys.columns and sys.types. However the max value will always be 4 for ints, since ints uses 4 bytes. In this case I'd rather have the highest numeric value of the column. I figured I might change my query to use DataLength for string-based columns, and a MAX() for number based columns, however I run into some problems before I even get there: Minified example code DECLARE #A bit = 1 SELECT CASE WHEN 1=1 THEN MAX(DATALENGTH(#A)) ELSE MAX(#A) END I would expect to receive the number 1 given that 1=1 is true. Instead I get an error Operand data type bit is invalid for max operator. I understand that you can't run MAX(#A) on a bit, but that's not what I'm trying to do. My goal is to run different aggregate functions depending on the datatype. How can I solve this?
My goal is to run different aggregate functions depending on the datatype. This will fail because you will get invalid cast errors or will get implicit conversions to the highest precedence data type Your use of bit is irrelevant here smalldatetime has the highest precedence so this code gives odd results when mixing datatypes DECLARE #foo table ( intval int, floatval float, datetimeval smalldatetime) INSERT #foo VALUES (1, 1.567E2, '2017-07-31'), (2, 2.0, '2017-08-01'); DECLARE #Switch int; SELECT CASE WHEN #Switch=1 THEN MAX(intval) WHEN #Switch=2 THEN MAX(floatval) ELSE MAX(datetimeval) END FROM #foo SET #Switch = 1 1900-01-03 00:00:00 SET #Switch = 2 1900-06-06 16:48:00 SET #Switch = 3 2017-08-01 00:00:00
In this case, you are missing a cast : SELECT CASE WHEN 1=1 THEN MAX(DATALENGTH(#A)) ELSE MAX(CAST(#A as bigint)) END
SQL Server - Cast invalid value to int
Is there any way to deal with SQL casts if the input data is corrupt? Let's say I have a column of datatype NVarchar(10) and want to cast this column to int. Let's also say that some of the nvarchar values are corrupt, so they can't be converted to int. Is there any way to silently ignore these, default them to 0 or some such?
DECLARE #t TABLE (Numbers VARCHAR(20)) INSERT INTO #t VALUES ('30a'),('30'),('100'), ('100a'),('200'),('200a') SELECT CASE WHEN ISNUMERIC(Numbers) = 1 THEN CAST(Numbers AS INT) ELSE NULL END AS Number FROM #t ISNUMERIC Function returns 1 when it is an integer value you can use this function. Result Number NULL 30 100 NULL 200 NULL it will cast the integer values to INT and ignore the values that cannot be cast to Int
Try this with PatIndex() function: select id, val from t where patindex('%[^0-9]%',val) = 0 Note: above query is filtering out corrupted values, if you need to bring them in with 0 values, please use a case expression as below. select id, case when patindex('%[^0-9]%',val) = 0 then convert(int, val) else 0 end val from t Fiddle demo for both queries
I'll be the unpopular one and advise REGEX because ISNUMERIC, while sometimes useful, doesn't catch everything. This answer on SO excellently covers some REGEX concepts, for instance: One numeric digit Probably the easiest one of the bunch: WHERE Column LIKE '[0-9]' For more details, here's a useful REGEX workbench by Phil Factor and Robyn Pae.
SQL Server : converting varchar to INT
I am stuck on converting a varchar column UserID to INT. I know, please don't ask why this UserID column was not created as INT initially, long story. So I tried this, but it doesn't work. and give me an error: select CAST(userID AS int) from audit Error: Conversion failed when converting the varchar value '1581............................................................................................................................' to data type int. I did select len(userID) from audit and it returns 128 characters, which are not spaces. I tried to detect ASCII characters for those trailing after the ID number and ASCII value = 0. I have also tried LTRIM, RTRIM, and replace char(0) with '', but does not work. The only way it works when I tell the fixed number of character like this below, but UserID is not always 4 characters. select CAST(LEFT(userID, 4) AS int) from audit
You could try updating the table to get rid of these characters: UPDATE dbo.[audit] SET UserID = REPLACE(UserID, CHAR(0), '') WHERE CHARINDEX(CHAR(0), UserID) > 0; But then you'll also need to fix whatever is putting this bad data into the table in the first place. In the meantime perhaps try: SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), '')) FROM dbo.[audit]; But that is not a long term solution. Fix the data (and the data type while you're at it). If you can't fix the data type immediately, then you can quickly find the culprit by adding a check constraint: ALTER TABLE dbo.[audit] ADD CONSTRAINT do_not_allow_stupid_data CHECK (CHARINDEX(CHAR(0), UserID) = 0); EDIT Ok, so that is definitely a 4-digit integer followed by six instances of CHAR(0). And the workaround I posted definitely works for me: DECLARE #foo TABLE(UserID VARCHAR(32)); INSERT #foo SELECT 0x31353831000000000000; -- this succeeds: SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), '')) FROM #foo; -- this fails: SELECT CONVERT(INT, UserID) FROM #foo; Please confirm that this code on its own (well, the first SELECT, anyway) works for you. If it does then the error you are getting is from a different non-numeric character in a different row (and if it doesn't then perhaps you have a build where a particular bug hasn't been fixed). To try and narrow it down you can take random values from the following query and then loop through the characters: SELECT UserID, CONVERT(VARBINARY(32), UserID) FROM dbo.[audit] WHERE UserID LIKE '%[^0-9]%'; So take a random row, and then paste the output into a query like this: DECLARE #x VARCHAR(32), #i INT; SET #x = CONVERT(VARCHAR(32), 0x...); -- paste the value here SET #i = 1; WHILE #i <= LEN(#x) BEGIN PRINT RTRIM(#i) + ' = ' + RTRIM(ASCII(SUBSTRING(#x, #i, 1))) SET #i = #i + 1; END This may take some trial and error before you encounter a row that fails for some other reason than CHAR(0) - since you can't really filter out the rows that contain CHAR(0) because they could contain CHAR(0) and CHAR(something else). For all we know you have values in the table like: SELECT '15' + CHAR(9) + '23' + CHAR(0); ...which also can't be converted to an integer, whether you've replaced CHAR(0) or not. I know you don't want to hear it, but I am really glad this is painful for people, because now they have more war stories to push back when people make very poor decisions about data types.
This question has got 91,000 views so perhaps many people are looking for a more generic solution to the issue in the title "error converting varchar to INT" If you are on SQL Server 2012+ one way of handling this invalid data is to use TRY_CAST SELECT TRY_CAST (userID AS INT) FROM audit On previous versions you could use SELECT CASE WHEN ISNUMERIC(RTRIM(userID) + '.0e0') = 1 AND LEN(userID) <= 11 THEN CAST(userID AS INT) END FROM audit Both return NULL if the value cannot be cast. In the specific case that you have in your question with known bad values I would use the following however. CAST(REPLACE(userID COLLATE Latin1_General_Bin, CHAR(0),'') AS INT) Trying to replace the null character is often problematic except if using a binary collation.
This is more for someone Searching for a result, than the original post-er. This worked for me... declare #value varchar(max) = 'sad'; select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint)); returns 0 declare #value varchar(max) = '3'; select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint)); returns 3
I would try triming the number to see what you get: select len(rtrim(ltrim(userid))) from audit if that return the correct value then just do: select convert(int, rtrim(ltrim(userid))) from audit if that doesn't return the correct value then I would do a replace to remove the empty space: select convert(int, replace(userid, char(0), '')) from audit
This is how I solved the problem in my case: First of all I made sure the column I need to convert to integer doesn't contain any spaces: update data set col1 = TRIM(col1) I also checked whether the column only contains numeric digits. You can check it by: select * from data where col1 like '%[^0-9]%' order by col1 If any nonnumeric values are present, you can save them to another table and remove them from the table you are working on. select * into nonnumeric_data from data where col1 like '%[^0-9]%' delete from data where col1 like '%[^0-9]%' Problems with my data were the cases above. So after fixing them, I created a bigint variable and set the values of the varchar column to the integer column I created. alter table data add int_col1 bigint update data set int_col1 = CAST(col1 AS VARCHAR) This worked for me, hope you find it useful as well.
TSQL to identify long Float values
I'm dealing with a legacy system where I need to identify some bad records based on a column with a data type of Float. Good records have a value of... 1 2 1.01 2.01 Bad records are anything such as.. 1.009999999999999 2.003423785643000 3.009999990463260 I've tried a number of select statements where I Convert to Decimal and cast to a varchar and use the LEN() function but this don't seem to work as the good records that are 1.01 become 1.0100000000000000 --Edit I'm a little closer now as I have discovered I can do (Weight * 100) and all of the good records become whole number values such as 101,201,265,301,500, etc... and bad ones such as 2.00999999046326 become 200.999999046326
This works on my SQL Server 2005 DB: select len(cast(cast(1.01 as float) as varchar)) Result: 4 In fact, it even lets me skip the explicit varchar cast if I want to: select len(cast(1.011 as float)) Result: 5 Update: First of all, I still needed the cast to varchar. Thinking otherwise was wrong. That said, I had this all working using strings and was about to post how. Then you I your update on mulitpling by 100 and realized that was the way to go. So here's my code for testing both ways: declare #test table ( c float) insert into #test select * from ( select 14.0100002288818 as c union select 1.01 union select 2.00999999046326 union select 14.01 ) t select c, case when c = cast(cast(c as varchar) as float) AND LEN(cast(c as varchar))<=5 then 1 else 0 end, case when c * 100 = floor(c * 100) then 1 else 0 end from #test
something like this, maybe? (adjust the precision/scale in the where clause, of course) select val from mytable WHERE CONVERT(decimal(5,2), val) <> val
Have you thought about using CLR integration and using .net to handle the validation see this link Basics of Using a .NET Assembly in MS SQL - User Functions basically you use .net methods as a user defined function to do the validation; .NET is better at working with numbers.
You could do something like this: SELECT * FROM YourTable WHERE CAST(YourFloatField AS DECIMAL(15,2)) <> YourFloatField I'm assuming that anything "bad" has more than 2 decimal places given.
This really will become a pain in the neck because floats are an imprecise datatype and you will get implicit conversions when casting. it also depends where you run something like the following select convert(float,1.33) in query analyzer the output is 1.3300000000000001 in SSMS the output is 1.33 when you convert to decimal you need to specify scale and precision so if you do select convert(decimal(10,6),convert(float,1.33)) you get this 1.330000 because you specified a scale of 6 you could do something like this where after converting to decimal you drop the trailing 0s select replace(rtrim(replace(convert(varchar(30), (convert(decimal(10,6),convert(float,1.33)))),'0',' ')),' ','0') for a value of 3.00999999046326 you need a scale of at least 14 select replace(rtrim(replace(convert(varchar(30), (convert(decimal(30,14),convert(float,3.00999999046326)))),'0',' ')),' ','0')
Run this: DECLARE #d FLOAT; SET #d = 1.23; SELECT ABS(CAST(#d AS DECIMAL(10,2)) - CAST(#d AS DECIMAL(15,8))); SET #d = 1.230000098; SELECT ABS(CAST(#d AS DECIMAL(10,2)) - CAST(#d AS DECIMAL(15,8))); Use some threshold such as: ABS(CAST(#d AS DECIMAL(10,2)) - CAST(#d AS DECIMAL(15,8)))<0.00001
SQL Server converting a variable varchar field to money/decimal/something with decimal places
I'm looking for an elegant way to convert a field of type varchar, with variable data in it, to a data type which can be used for mathematical operations sample data from the field (excluding quotes) '' 'abc' '23' '23.2' The method should work for all, and for the first & second values should return 0, and not throw an SQL Server error..
Try this: SELECT CASE WHEN IsNumeric(YourColumn) = 0 THEN 0 ELSE CAST(YourColumn AS decimal(18, 2)) END You have to adjust the destination data type, I have chosen decimal(18, 2) for demonstration.
I know this is a long-dead thread, but I recently stumbled upon it from a Google search and had a thought. It is less elegant than a CASE statement, but it is an alternative. SELECT COALESCE(CAST(NULLIF(ISNUMERIC(LEFT(MyColumn, PATINDEX('% %', MyColumn + ' ') - 1)), 1) AS MONEY), LEFT(MyColumn, PATINDEX('% %', MyColumn + ' ') - 1)) FROM myTable or you could do: Select COALESCE(CAST(NULLIF(ISNUMERIC(MyColumn), 1) AS MONEY), MyColumn) FROM myTable The top version would see "2 5" as just 2, the bottom one would see it as a text field.
SELECT CASE IsNumeric(mycol) WHEN 1 THEN CAST(mycol AS FLOAT) ELSE 0 END FROM mytable
If you'd like to convert it, you should use UPDATE instead of SELECT UPDATE Table SET Col1 = CAST(Col1 As Decimal(18,2))
COALESCE is a great option for this: Find more information here. It evaluates the arguments in order and returns the current value of the first expression that initially does not evaluate to NULL. ISNUMERIC returns 0 or 1 depending on if the value being evaluated could be considered one of the SQL 'number' or 'numeric' types. e.g. int, bigint, money.. NULLIF essentially finds the value you specify and if it matches it replaces it with a NULL value. CAST Simply changes a data type to another in this example to MONEY As you can see, if you break the below down using this information its quite an elegant solution I think? COALESCE(CAST(NULLIF(ISNUMERIC(COL1), 1) AS MONEY), COL1)