TSQL to identify long Float values - sql-server

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

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

How do you write a SQL query to find all the rows that has a float currency value like $15.34 in a nvarchar field

How do you write a SQL query to find only the rows that has a float currency value like $15.34 and NOT round currency value like 15 in a nvarchar field.
Assuming you have a mix of numeric and non-numeric, this should work to return all decimal values that are not whole dollar amounts:
Select * from tablename
where colname like '%.%' --Has a decimal (as in original query)
and colname not like '%.00' --Does not end with 00
It is as simple as
Select * from tablename where columnname = '15.34'
I would strip the $ out, and check if it evaluates to a numeric or not, and use a modulo to be sure a remainder remains when divided by 1.
DECLARE #TEST TABLE (columnname NVARCHAR(15))
INSERT INTO #TEST
SELECT '$15.34' UNION
SELECT 'ZERO' UNION
SELECT '$123.00'
SELECT *, CONVERT(MONEY,REPLACE(columnname,'$',''))
FROM #TEST
WHERE ISNUMERIC(REPLACE(columnname,'$',''))=1
AND CONVERT(MONEY,REPLACE(columnname,'$','')) % 1 != 0
You can use like
Select * from Yourtablename where Yourcolumnname like '$15.%'
Two things:
First, you want to find the rows having the $ in them.
WHERE LOCATE('$',columname) <> 0
Second, you want to find the rows where the rest of the value in the column is a floating point number.
AND CONVERT(REPLACE(columnname,'$',''),DECIMAL(10,2)) <> 0
That CONVERT() <>0 pattern works because MySQL silently returns zero when you try to convert a nonnumeric value to a number.

SQL Server Conversion error: Conversion failed when converting the nvarchar value 'XXX' to data type int

Running some pretty simple SQL here:
select *
from table
where columnA <> convert(int,columnB)
and isnumeric(columnB) = 1
Still getting this error every time:
Conversion failed when converting the nvarchar value 'XXX' to data type int.
If you're using SQL Server 2012 or more recent you could use TRY_PARSE which will return NULL when the parse fails.
SELECT TRY_PARSE('one' as int) -- NULL
, TRY_PARSE('1' as int) -- 1
, TRY_PARSE('0.1' as int) -- NULL
Returns the result of an expression, translated to the requested data type, or null if the cast fails in SQL Server. Use TRY_PARSE only for converting from string to date/time and number types.
Isnumeric has a lot of odd behavior. For example, it also considers currency signs such as $ or £, and even a hyphen (-) to be numeric.
I think you'd be better of using NOT columnB like '%[^0-9]%' to ONLY take numbers into account.
Check the comments at the bottom of the msdn page for isnumeric(), which you can find here: https://msdn.microsoft.com/en-us/library/ms186272.aspx
This may sound weird, but it breaks when do not put the ISNUMERIC check first. Try this out:
WITH [Table]
AS
(
SELECT columnA,columnB
FROM
(
VALUES (1,'2'),
(2,'XXX')
) A(columnA,columnB)
)
select *
from [Table]
where ISNUMERIC(columnB) = 1 --this works
AND columnA <> convert(int,columnB)
--where columnA <> convert(int,columnB) --this doesn't work
-- and isnumeric(columnB) = 1
I suggest you to reverse your checking like this:
SELECT *
FROM table
WHERE CONVERT(NVARCHAR, columnA) <> columnB
I got this using a combination of the answers and comments here. I used a CASE statement in my WHERE clause and also had to use LIKE instead of ISNUMERIC to account for illegal characters. I also had to use BIGINT because a few select samples were overflowing the INT column. Thanks for all of the suggestions everybody!
select * from patient
where PatientExternalID <>
(case when mrn not like '%[^0-9]%'
then convert(bigint, mrn)
else 0
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.

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?
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

Resources