Convert Negative Varchar, Null to Decimal in SQL Server - sql

I am trying to convert one of my measure column values having below from varchar to decimal(20,3). The 3rd value is a null value without any space and to make you understand I enclosed with quotes. I have tried with CAST but CAST is not able to change datatype as having negative sign. But I want to keep negative value.
I tried using TRY_CONVERT and TRY_CAST but that is not giving desired result. If anyone can help will be really helpful. I have tried below
CAST(COALESCE((NULLIF(SalesColumn,'')),'0') AS NUMERIC(20,12))
-992.0
0

I use SQL Server 2017 and CAST works just fine and maintains the "-"-sign, below is an example. Beware of the rounding, you might need to use ROUND() before converting it to decimal(20,3) by first converting it to a decimal with higher precision, using ROUND() and then casting it to decimal(20,3).
Using CAST to numeric or decimal works just fine on NULL as well.
DECLARE
#Val1 VARCHAR(10) = '-992.123'
,#Val2 VARCHAR(10) = '-45678'
,#Val3 VARCHAR(10) = NULL
,#Val4 VARCHAR(10) = ''
,#Val5 VARCHAR(10) = '321'
,#Val6 VARCHAR(10) = ' '
SELECT ISNULL(CAST(NULLIF(A, '') as decimal(20,3)), 0)
FROM (
VALUES (#Val1), (#Val2), (#Val3), (#Val4), (#Val5), (#Val6)
) Sub (A)
Which gives the result..
---------------------------------------
-992.123
-45678.000
0.000
0.000
321.000
0.000
(6 rows affected)

You can add another coalesce() within nullif() to handle null value. Please look into below examples.
declare #var as varchar(50);
set #var=null;
select cast(COALESCE((NULLIF(coalesce(trim(#var),0),'')),'0') AS NUMERIC(20,12))
GO
| (No column name) |
| ---------------: |
| 0.000000000000 |
declare #var as varchar(50);
set #var=' ';
select cast(COALESCE((NULLIF(coalesce(trim(#var),0),'')),'0') AS NUMERIC(20,12))
GO
| (No column name) |
| ---------------: |
| 0.000000000000 |
declare #var as varchar(50);
set #var='- 992 ';
select cast(COALESCE((NULLIF(coalesce(trim(#var),0),'')),'0') AS NUMERIC(20,12))
GO
| (No column name) |
| ----------------: |
| -992.000000000000 |
db<>fiddle here

Related

MS SQL case statement and cast/covert data type issues

I'm having an issue with the MS SQL case statement that has cast inside. Here is the example I cam up with.
DECLARE #bla as varchar(10) = '001234'
DECLARE #vb AS varchar(20) = 'bla'
SELECT CASE when (#vb <> 'bla') THEN CAST(#bla AS int) ELSE #bla END vbla
The result is very strange. It should be 001234. What am I missing?
+------+
| vbla |
+------+
| 1234 |
+------+
A case EXPRESSION (not statement) returns a single type. When one of the branches is a number, then the return value is a number.
The value you are seeing is the number that the string converts to. If the string started with a non-digit, then the value would be 0.
If you want to see the leading zeros, leave the value as a string.

Way of rounding of currency in SQL Server

Given the following values (float data type), I want to ROUND them in the following way:
+----------------+--------+
| Original Value | Result |
+----------------+--------+
| 53.36 | 53.40 |
| 53.34 | 53.30 |
| 53.35 | 53.35 | --Do not round up when 5
+----------------+--------+
Is there is a way to do this using T-SQL?
Since you need your value to remain xx.x5 when found but ROUND otherwise, the following will work for you:
(IIF available in SQL Server 2012+)
DECLARE #val FLOAT = 53.35;
SELECT IIF ((RIGHT(#val,1) = 5), #val, ROUND(#val,1)) result
SET #val = 53.34
SELECT IIF ((RIGHT(#val,1) = 5), #val, ROUND(#val,1)) result
SET #val = 53.36
SELECT IIF ((RIGHT(#val,1) = 5), #val, ROUND(#val,1)) result
Here is a good resource to read about differences of types, specifically read about float types. Seems that's possibly not a good datatype for the values you have. Just something to consider.

How to convert varchar(4) to float in SQL Server 2008?

I'm trying to convert my database fields from VARCHAR(4) to FLOAT. Some of the values in these fields might not be digits since these fields didn't have any validation prior. My main target is to convert any integer or decimal value in float format and save in new database field. For this process I use INSERT SELECT STATEMENT from old table into the new table. So far I have this line of code for my conversion:
CASE WHEN LEN(LTRIM(RTRIM(hs_td2))) <> 0 AND ISNUMERIC(hs_td2) = 1 THEN CAST(LTRIM(RTRIM(hs_td2)) AS float) ELSE NULL END AS hs_td2
First step I trim the value then check if it's numeric and then convert to float otherwise set to NULL. With the code above I'm getting this error message in Microsoft Studio:
Msg 8114, Level 16, State 5, Line 13
Error converting data type varchar to float.
Line 13th is beginning of my SELECT statement. Then I tried this conversion as well:
CASE WHEN LEN(LTRIM(RTRIM(hs_td2))) <> 0 AND ISNUMERIC(hs_td2) = 1 THEN CONVERT(FLOAT, LTRIM(RTRIM(hs_td2))) ELSE NULL END AS hs_td2
and I got the same error message. Values in my fields could be something like this:
10 or 5 or -10 or 0.9 or 11.6 or -11.89 and so on...
I'm wondering if isNumeric() is the best function that I should use and why my code produces the error message listed above?
If anyone can help please let me know. Thank you!
No, ISNUMERIC is not the best function to use.
Essentially, this question has been asked before, though not in this wording:
Try_Convert for SQL Server 2008 R2
The most upvoted answer recommends to cast to XML to use XML-specific casting function:
DECLARE #T TABLE (v varchar(4));
INSERT INTO #T (v) VALUES
('1g23'),
('-1.8'),
('11.6'),
('akjh'),
('.'),
('-'),
('$'),
('12,5');
select
cast('' as xml).value('sql:column("V") cast as xs:float ?', 'float') as new_v
from #T
I'll leave my first version of the answer below.
Most likely you are getting the conversion error because the server tries to run CAST(LTRIM(RTRIM(hs_td2)) AS float) for each row of the table, not only for those that are numeric.
This usually happens when you try to filter out non-numeric rows using the WHERE ISNUMERIC(...) = 1 filter. Technically it may happen in CASE expression as well.
That's why they added TRY_CONVERT in 2012.
I'd try to write my own user-defined function that uses TRY-CATCH and tries to convert the given value. Yes, it will be slow.
Having said that, the example below with CASE runs fine:
DECLARE #T TABLE (v varchar(4));
INSERT INTO #T (v) VALUES
('123'),
('-1.8'),
('11.6'),
('akjh'),
('123');
SELECT
CASE WHEN ISNUMERIC(v) = 1 THEN CAST(v AS float) ELSE NULL END AS new_v
FROM #T;
Result
+-------+
| new_v |
+-------+
| 123 |
| -1.8 |
| 11.6 |
| NULL |
| 123 |
+-------+
But, if I put a . or - or $ value, like so:
INSERT INTO #T (v) VALUES
('123'),
('-1.8'),
('11.6'),
('akjh'),
('$');
The query fails:
Error converting data type varchar to float.
There may be other special characters and their combinations that ISNUMERIC would not complain about. That's why I originally said that overall, ISNUMERIC is not the best function to use.
If it is a one-off conversion, you can try to build a LIKE expression to catch all special cases that are present in your data, but if you need a reliable generic solution, upgrade to 2012+ and use TRY_CONVERT or write your T-SQL UDF, or your CLR UDF.
Sqlxml has enough power to make magic. Of course, the performance is the problem. But still better, than million of conditions
DECLARE #T TABLE (v varchar(4));
INSERT INTO #T (v) VALUES
('123'),('-1.8'),('11.6'),('akjh'),('$'),('-.'),('-.1'),(NULL);
declare #Xml xml = (SELECT v FROM #T T for xml auto,elements);
select T.v.value('v[1]','varchar(4)') v, T.v.value('max(v[1])','float') converted_v
from #xml.nodes('/T') T(v);
It depends on the values in your varchar columns
ISNUMBER() for vaule such as '.' and '-' will return 1, however, it will failed when you CAST to FLOAT
ISNUMBER() for value such as '3D2' , '1e2' will return 1, and can be CAST to FLOAT, however, you may not want consider it as number.
You may try the following to convert
CASE WHEN
not LTRIM(RTRIM(hs_td2))like '%[^0-9.,-]%' -- Value only contains 0-9 . -
and LTRIM(RTRIM(hs_td2)) not like '.' -- can not be only .
and LTRIM(RTRIM(hs_td2)) not like '-' -- can not be only -
and isnumeric(LTRIM(RTRIM(hs_td2))) = 1
THEN CAST(LTRIM(RTRIM(hs_td2)) AS float)
ELSE NULL
END

Smart CONVERT float to VARCHAR in T-SQL

I have the following code
DECLARE #m FLOAT=213456789.55
DECLARE #sql VARCHAR(MAX)='INSERT INTO Test VALUES('+CONVERT(VARCHAR,#m,1)+')'
EXEC(#sql)
but the result is 213456790 instead of 213456789.55
When I try to write CONVERT(VARCHAR,213456789.55,1) it then returns 213456789.55 not 213456790
How do I solve this?
EDITS
Declaring #m as Decimal like following DECLARE #m DECIMAL(18,2)=213456789.55 solved the issue but I want to know if there is another solution for using float. Thanks
You can use STR instead of CONVERT. STR allows to specify number of decimal places to the right of the decimal point.
DECLARE #m FLOAT=213456789.55;
SELECT
#m AS [Float]
,CONVERT(VARCHAR(100),#m,0) AS Convert0
,CONVERT(VARCHAR(100),#m,1) AS Convert1
,CONVERT(VARCHAR(100),#m,2) AS Convert2
,LTRIM(STR(#m, 20, 2)) AS [Str]
Result (SQL Server 2008)
+--------------+--------------+----------------+------------------------+--------------+
| Float | Convert0 | Convert1 | Convert2 | Str |
+--------------+--------------+----------------+------------------------+--------------+
| 213456789.55 | 2.13457e+008 | 2.1345679e+008 | 2.134567895500000e+008 | 213456789.55 |
+--------------+--------------+----------------+------------------------+--------------+
CONVERT always uses scientific notation for float types.
try this:
DECLARE #m FLOAT=213456789.55
DECLARE #sql VARCHAR(MAX)='INSERT INTO Test VALUES('+CONVERT(VARCHAR,CAST(#m AS MONEY),1)+')'
EXEC(#sql)

Replace non-numeric characters with numbers

I have a column that looks like:
Column A
1A
2B
5Z
Essentially, I need to replace the letter part with the correspending number in the alphabet. LIke:
Column A
1.1
2.2
5.26
I was thinking of creating a "lookup" table to retrieve the correspnding letter number but wanted to see if there is a more elegant/efficient way?
Many thanks
Just give you an algorithm to convert character to number and let parsing characters within a column up to you:
DECLARE #offset int
SET #offset = 64
DECLARE #character char
-- OUTPUT | A | 1 |
SET #character = 'A'
SELECT #character, ASCII(#character) - #offset
-- OUTPUT | Z | 26 |
SET #character = 'Z'
SELECT #character, ASCII(#character) - #offset