{"id":3224,"date":"2011-01-13T06:34:00","date_gmt":"2011-01-13T06:34:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/isnumeric-broken-only-up-to-a-point\/"},"modified":"2017-10-12T16:12:38","modified_gmt":"2017-10-12T16:12:38","slug":"isnumeric-broken-only-up-to-a-point","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/isnumeric-broken-only-up-to-a-point\/","title":{"rendered":"IsNumeric() Broken?  Only up to a point."},"content":{"rendered":"<p>In SQL Server, probably the best-known &#8216;broken&#8217; function is poor ISNUMERIC() . The documentation says<\/p>\n<blockquote>\n<p>&#8216;ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0. ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($).&#8217;<\/p>\n<\/blockquote>\n<p>Although it will take numeric data types (No, I don&#8217;t understand why either), its main use is supposed to be to test strings to make sure that you can convert them to whatever numeric datatype you are using (int, numeric, bigint, money, smallint, smallmoney, tinyint, float, decimal, or real). It wouldn&#8217;t actually be of much use anyway, since each datatype has different rules. You actually need a RegEx to do a reasonably safe check. The other snag is that the IsNumeric() function\u00a0 is a bit broken.<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT ISNUMERIC(',')<\/pre>\n<p>This cheerfully returns 1, since it believes that a comma is a currency symbol (not a thousands-separator) and you meant to say 0, in this strange currency.\u00a0 However,<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT ISNUMERIC(N'\u20a4')<\/pre>\n<p>isn&#8217;t recognized as currency.\u00a0 &#8216;+&#8217; and\u00a0 &#8216;-&#8216; is seen to be numeric, which is stretching it a bit. You&#8217;ll see that what it allows isn&#8217;t really broken except that it doesn&#8217;t recognize Unicode currency symbols: It just tells you that one numeric type is likely to accept the string if you do an explicit conversion to it using the string. Both these work fine, so poor IsNumeric has to follow suit.<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT  CAST('0E0' AS FLOAT)\r\nSELECT  CAST (',' AS MONEY)<\/pre>\n<p>but it is harder to predict which data type will accept a &#8216;+&#8217; sign.<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT  CAST ('+' AS money) --0.00\r\n\r\nSELECT  CAST ('+' AS INT)   --0\r\n\r\nSELECT  CAST ('+' AS numeric)\r\n\/* Msg 8115, Level 16, State 6, Line 4 \r\nArithmetic overflow error converting varchar to data type numeric.*\/\r\n\r\nSELECT  CAST ('+' AS FLOAT)\r\n\/*Msg 8114, Level 16, State 5, Line 5\r\nError converting data type varchar to float.\r\n*\/&gt;<\/pre>\n<p>So we can begin to say that the maybe IsNumeric isn&#8217;t really broken, but is answering a silly question &#8216;Is there some numeric datatype to which i can convert this string? Almost, but not quite. The bug is that it doesn&#8217;t understand Unicode currency characters such as the euro or franc which are actually valid when used in the CAST function. (perhaps they&#8217;re delaying fixing the euro bug just in case it isn&#8217;t necessary).<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT ISNUMERIC (N'\u20a323.67') --0\r\nSELECT  CAST (N'\u20a323.67' AS money) --23.67\r\nSELECT ISNUMERIC (N'\u00a3100.20') --1\r\nSELECT  CAST (N'\u00a3100.20' AS money) --100.20\r\n<\/pre>\n<p>Also the CAST function itself is quirky in that it cannot convert perfectly reasonable string-representations of integers into integers<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT ISNUMERIC('200,000')       --1\r\nSELECT  CAST ('200,000' AS INT)   --0\r\n\/*Msg 245, Level 16, State 1, Line 2\r\nConversion failed when converting the varchar value '200,000' to data type int.\r\n*\/<\/pre>\n<p>A more sensible question is &#8216;Is this an integer or decimal number&#8217;. This cuts out a lot of the apparent quirkiness. We do this by the &#8216;+E0&#8217; trick. If we want to include floats in the check, we&#8217;ll need to make it a bit more complicated. Here is a small test-rig.<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT  PossibleNumber, \r\n        ISNUMERIC(CAST(PossibleNumber AS NVARCHAR(20)) + 'E+00') AS Hack,\r\n        ISNUMERIC (PossibleNumber + CASE WHEN PossibleNumber LIKE '%E%' \r\n                                         THEN '' ELSE 'E+00' END) AS Hackier,\r\n        ISNUMERIC(PossibleNumber) AS RawIsNumeric\r\nFROM    (SELECT CAST(',' AS NVARCHAR(10)) AS PossibleNumber \r\n         UNION SELECT '\u00a3' UNION SELECT '.'\r\n         UNION SELECT '56' UNION SELECT '456.67890'\r\n         UNION SELECT '0E0' UNION SELECT '-'\r\n         UNION SELECT '-' UNION SELECT '.'\r\n         UNION  SELECT N'\u20b5' UNION SELECT N'\u00a2'\r\n         UNION  SELECT N'\u20a0' UNION SELECT N'\u20a034.56'\r\n         UNION SELECT '-345' UNION SELECT '3.332228E+09')\r\n         AS examples<\/pre>\n<p>Which gives the result &#8230;<\/p>\n<pre class=\"lang:tsql decode:true\">PossibleNumber Hack        Hackier     RawIsNumeric\r\n-------------- ----------- ----------- ------------\r\n\u20b5              0           0           0\r\n-              0           0           1\r\n,              0           0           1\r\n.              0           0           1\r\n\u00a2              0           0           1\r\n\u00a3              0           0           1\r\n\u20a0              0           0           0\r\n\u20a034.56         0           0           0\r\n0E0            0           1           1\r\n3.332228E+09   0           1           1\r\n-345           1           1           1\r\n456.67890      1           1           1\r\n56             1           1           1<\/pre>\n<p>I suspect that this is as far as you&#8217;ll get before you abandon IsNumeric in favour of a regex. You can only get part of the way with the LIKE wildcards, because you cannot specify quantifiers. You&#8217;ll need full-blown Regex strings like these ..<\/p>\n<pre class=\"lang:tsql decode:true\">[-+]?\\b[0-9]+(\\.[0-9]+)?\\b #INT or REAL\r\n[-+]?\\b[0-9]{1,3}\\b #TINYINT\r\n[-+]?\\b[0-9]{1,5}\\b #SMALLINT<\/pre>\n<p>.. but you&#8217;ll get even these to fail to catch numbers out of range.<br \/>\n So is IsNumeric() an out and out rogue function? Not really, I&#8217;d say, but then it would need a damned good lawyer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In SQL Server, probably the best-known &#8216;broken&#8217; function is poor ISNUMERIC() . The documentation says &#8216;ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0. ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as&#8230;&hellip;<\/p>\n","protected":false},"author":154613,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[],"coauthors":[6813],"class_list":["post-3224","post","type-post","status-publish","format-standard","hentry","category-blogs"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/154613"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=3224"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3224\/revisions"}],"predecessor-version":[{"id":74354,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3224\/revisions\/74354"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=3224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=3224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=3224"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=3224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}