8

The reserve column is a varchar, to perform sums on it I want to cast it to a deciaml. But the SQL below gives me an error

select
cast(Reserve as decimal)
from MyReserves

Error converting data type varchar to numeric.

I added the isnumeric and not null to try and avoid this error but it still persists, any ideas why?

select
cast(Reserve as decimal)
from MyReserves
where isnumeric(Reserve ) = 1
and MyReserves is not null
Hoody
  • 3,361
  • 9
  • 45
  • 55
  • 3
    Can you show some data in Reserve column ? – Upendra Chaudhari Sep 27 '11 at 10:46
  • 2
    You have to be carefull with isnumeric, as it might return results that are incorrect. This is from the documentation **ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($). ** Have a look at http://stackoverflow.com/questions/312054/efficient-isnumeric-replacements-on-sql-server – Adriaan Stander Sep 27 '11 at 10:50
  • 2
    @astander - it doesn't return results that are incorrect. It's just that most people don't appreciate that it answers a question that no-one ever actually wants to ask ("will this string convert to at least one of the numeric types?") – Damien_The_Unbeliever Sep 27 '11 at 10:52
  • 5
    You seem to be assuming that the `cast` will happen after the `where`. This is absolutely not guaranteed in any event irrespective of the limitations of `isnumeric` – Martin Smith Sep 27 '11 at 10:58
  • @MartinSmith - I think you should add this as an answer – Ed Harper Sep 27 '11 at 11:26
  • @EdHarper - The reason I didn't is because there are already plenty of dupes for this case. [For example this one](http://stackoverflow.com/q/7263501/73226) – Martin Smith Sep 27 '11 at 11:44

11 Answers11

10

See here: CAST and IsNumeric

Try this:

WHERE IsNumeric(Reserve + '.0e0') = 1 AND reserve IS NOT NULL

UPDATE

Default of decimal is (18,0), so

declare @i nvarchar(100)='12121212121211212122121'--length is>18 
SELECT ISNUMERIC(@i) --gives 1
SELECT CAST(@i as decimal)--throws an error
Community
  • 1
  • 1
Dalex
  • 3,585
  • 19
  • 25
7

Gosh, nobody seems to have explained this correctly. SQL is a descriptive language. It does not specify the order of operations.

The problem that you are (well, were) having is that the where does not do the filtering before the conversion takes place. Order of operations, though, is guaranteed for a case statement. So, the following will work:

select cast(case when isnumeric(Reserve) = 1 then Reserve end as decimal)
from MyReserves
where isnumeric(Reserve ) = 1 and MyReserves is not null

The issue has nothing to do with the particular numeric format you are converting to or with the isnumeric() function. It is simply that the ordering of operations is not guaranteed.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
4

Use try_cast (sql 2012)

select
try_cast(Reserve as decimal)
from MyReserves
renegm
  • 610
  • 2
  • 5
  • 12
4

It seems that isnumeric has some Problems:

http://www.sqlhacks.com/Retrieve/Isnumeric-problems (via internet archive)

According to that Link you can solve it like that:

select
cast(Reserve as decimal)
from MyReserves
where MyReserves is not null
and MyReserves * 1 = MyReserves 
takrl
  • 6,356
  • 3
  • 60
  • 69
Salazaar
  • 384
  • 1
  • 9
  • Lets try: declare @i nvarchar(100)='+' IF ISNUMERIC(@i)=1 AND @i*1=@i PRINT 'Numeric' ELSE PRINT 'NON-Numeric' ---- result:Numeric (Incorrect) It is because SELECT ('+' * 1) gives to you '0' – Dalex Sep 27 '11 at 11:03
2

IsNumeric is a problem child -- SQL 2012 and later has TRY_CAST and TRY_CONVERT

If you're on an earlier version then you can write a function that'll convert to a decimal (or NULL if it won't convert). This uses the XML conversion functions that don't throw errors when the number won't fit ;)

-- Create function to convert a varchar to a decimal (returns null if it fails)
IF EXISTS( SELECT * FROM sys.objects WHERE object_id = OBJECT_ID( N'[dbo].[ToDecimal]' ) AND type IN( N'FN',N'IF',N'TF',N'FS',N'FT' ))
    DROP FUNCTION [dbo].[ToDecimal];
GO

CREATE FUNCTION ToDecimal
(   
    @Value VARCHAR(MAX)
)
RETURNS DECIMAL(18,8)
AS
BEGIN
    -- Uses XML/XPath to convert @Value to Decimal because it returns NULL it doesn't cast correctly
    DECLARE @ValueAsXml XML
    SELECT @ValueAsXml = Col FROM (SELECT (SELECT @Value as Value FOR XMl RAW, ELEMENTS) AS Col) AS test
    DECLARE @Result DECIMAL(38,10)
    -- XML/XPath will return NULL if the VARCHAR can't be converted to a DECIMAL(38,10)
    SET @Result =  @ValueAsXml.value('(/row/Value)[1] cast as xs:decimal?', 'DECIMAL(38,10)')
    RETURN CASE -- Check if the number is within the range for a DECIMAL(18,8)
        WHEN @Result >= -999999999999999999.99999999 AND @Result <= 999999999999999999.99999999
            THEN CONVERT(DECIMAL(18,8),@Result) 
        ELSE 
            NULL
    END
END

Then just change your query to:

select dbo.ToDecimal(Reserve) from MyReserves
Derek Tomes
  • 3,989
  • 3
  • 27
  • 41
1

Just a heads up on isnumeric; if the string contains some numbers and an 'E' followed by some numbers, this is viewed as an exponent. Example, select isnumeric('123E0') returns 1.

Sean Gahan
  • 11
  • 2
  • It also does the same for 'D' Try: SELECT IsNumeric( '123D45') As [Useless Function] – Antony Booth Apr 26 '15 at 17:09
  • wow. i cant believe it. the documentation doesnt mention this at all https://learn.microsoft.com/en-us/sql/t-sql/functions/isnumeric-transact-sql this is the problem i was having too. i think im gonna replace it with try_cast – user2565663 Mar 04 '18 at 04:45
1

I had this same problem and it turned out to be scientific notation such as '1.72918E-13' To find this just do where Reserve LIKE '%E%'. Try bypassing these and see if it works. You'll have to write code to convert these to something usable or reformat your source file so it doesn't store any numbers using scientific notation.

1

isnumeric is not 100% reliable in SQL - see this question Why does ISNUMERIC('.') return 1?

I would guess that you have value in the reserve column that passes the isnumeric test but will not cast to decimal.

Community
  • 1
  • 1
ipr101
  • 24,096
  • 8
  • 59
  • 61
0

I am also facing this issue and I solved by below method. I am sharing this because it may helpful to some one.

declare @g varchar (50)
 set @g=char(10)
 select isnumeric(@g),@g, isnumeric(replace(replace(@g,char(13),char(10)),char(10),''))
Singaravelan
  • 809
  • 3
  • 19
  • 32
0

IsNumeric is possibly not ideal in your scenario as from the highlighted Note on this MSDN page it says "ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($)."

Also there is a nice article here which further discusses ISNUMERIC.

Barry Kaye
  • 7,682
  • 6
  • 42
  • 64
0

Try (for example):

select
cast(Reserve as decimal(10,2))
from MyReserves

Numeric/Decimal generally want a precision an scale.

Larry Lustig
  • 49,320
  • 14
  • 110
  • 160