Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CAST and IsNumeric

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?

like image 260
Mark Bostleman Avatar asked Dec 03 '08 17:12

Mark Bostleman


People also ask

What is difference between CAST and convert in SQL?

The CAST function is used to convert a data type without a specific format. The CONVERT function does converting and formatting data types at the same time.

What can I use instead of IsNumeric?

Avoid using the IsNumeric() function, because it can often lead to data type conversion errors, when importing data. On SQL Server 2012 or later, use the Try_Convert() or Try_Cast() function instead. On earlier SQL Server versions, the only way to avoid it is by using LIKE expressions.

What is IsNumeric?

IsNumeric ( expression ) The required expressionargument is a Variant containing a numeric expression or string expression. Remarks. IsNumeric returns True if the entire expression is recognized as a number; otherwise, it returns False.

What is CAST () and convert () functions in SQL Server?

The cast and convert functions provide similar functionality. They are used to convert a value from one data type to another. So let's take a look at a practical example. The example is developed in SQL Server 2012 using the SQL Server Management Studio.


2 Answers

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 
like image 120
George Mastros Avatar answered Nov 12 '22 21:11

George Mastros


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.

  1. Some results are free-texted.
  2. Some are Enumerations (Yes, No, Blue, Black, etc..).
  3. Some are Integers.
  4. Others use decimals.
  5. 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. MikeTeeVee's IsNumeric Casting

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. 
like image 27
MikeTeeVee Avatar answered Nov 12 '22 19:11

MikeTeeVee