MS SQL case statement and cast/covert data type issues - sql

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.

Related

Convert Negative Varchar, Null to Decimal in SQL Server

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

SQL "select case" gives unexpected output

For the love of god can someone please explain me what is going on here.
I am working on some stored procedure bug fixes, after a lot of struggle, I was able to find out where this strange bug is happening so I've made a simple example for the sake of demonstration.
Case 1
DECLARE #test VARCHAR(45) ='0001'
SELECT CASE 0
WHEN -1 THEN ''
WHEN 0 THEN #test
WHEN 1 THEN 12345
END AS 'output'
Case 2
DECLARE #test VARCHAR(45) ='0001'
SELECT CASE 0
WHEN -1 THEN ''
WHEN 0 THEN #test
END AS 'output'
Case 1: output is 1
Case 2: output is 0001 as expected
What happened to the zeros?
strangely it only removes zeros before the number (no matter what number comes at the end), for example DECLARE #test VARCHAR(45) ='1000' works fine, as far case is concerned it is clear that case is zero, if I remove WHEN 0 THEN #test output is null as expected.
Done some research, found only this.
I am using SQL SERVER RC1 2017 as well as SQL SMS.
Thank you for your time.
The difference here is that you have mutliple data types in your THEN/ELSE expressions. In your "CASE 1" you have both int and and varchar datatypes.
int has a higher Data type precedence (Transact-SQL) than a varchar, so the values are returned as an int.
Use the same datatype through out, and this works as you want it to:
DECLARE #test VARCHAR(45) ='0001';
SELECT CASE 0 WHEN -1 THEN ''
WHEN 0 THEN #test
WHEN 1 THEN '12345' --Note that the value in contained in single quotes
END AS [output];
This is because of data type precedence.
The CASE expression documentation says about the returned data type:
Returns the highest precedence type from the set of types in
result_expressions and the optional else_result_expression
Meaning it looks every set of types in the result expressions. Since you have one where the returned data is an INT, then the result of the whole CASE expression is INT (since it has a higher precedence than VARCHAR)
This is happening because the type of the value returned in your last case statement is numeric, so try to change it to
DECLARE #test VARCHAR(45) ='0001'
SELECT CASE 0
WHEN -1 THEN ''
WHEN 0 THEN #test
WHEN 1 THEN '12345'
END AS 'output'

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

How to convert Varchar column to Numeric

I have a requirement to move varchar column data to Numeric but with two conditions.
All the alphanumeric value should migrate as null
All the decimal values should go as it is.
I wrote the condition as WHERE data like '%[^0-9]%', it is working fine for all the records except for decimal.
Also I have values like .001 abcd, this has to be pass as null.
To summarize I need :
1) 1234 as 1234
2) 1.23 as 1.23
3) ABC as null
4) .ABC as null
There is by default function in SQL Server ISNUMERIC() so, first of all Check your data value by that function,
Select ISNUMERIC(DATA)
Whole query is written as below,
SELECT CASE WHEN ISNUMERIC(data)=1 THEN CAST(data as decimal(18,2))
ELSE NULL END as tData FROM DataTable
As per your question,first we have to convert with numeric with using case,which satisfies your first condition,another thing if the value is String than convert as NULL. In Above query both the condition has been taken care.
EDIT : If you are using SQL SERVER 2012 or higher version then use
TRY_PARSE(), then there will be no need to worry about using CASE too...
I have tried this,
SELECT TRY_PARSE('63.36' as decimal(18,2)) got result 63.36
and
SELECT TRY_PARSE('.' as decimal(18,2)) got result NULL
I think that this fits your spec. It is quite verbose, but hopefully it breaks down the conditions sufficiently that it's clearly doing the correct thing or, if it isn't, that it's easy enough to modify:
declare #t table (data varchar(30))
insert into #t(data) values
('1234'),
('1.23'),
('abc'),
('.abc'),
('+6000'),
('1.2.3')
select
CASE WHEN
Possible = 1 AND
(DecCheck = 0 OR
SingleDec = 1
) THEN
CONVERT(decimal(12,3),data)
END
from
#t t
cross apply
(select
--Only contains the correct characters
CASE WHEN not t.data like '%[^0-9.]%' THEN 1 ELSE 0 END as Possible,
--Contains a decimal point? (Needs more checks)
CASE WHEN CHARINDEX('.',t.data) > 0 THEN 1 ELSE 0 END as DecCheck,
CHARINDEX('.',t.data) as FirstDec --Where the first decimal point is
) p
cross apply
(select
CASE WHEN DecCheck = 1 THEN
--Only contains one decimal point
CASE WHEN LEN(data) = FirstDec + CHARINDEX('.',REVERSE(data)) - 1
THEN 1
ELSE 0 END
ELSE 0 END as SingleDec
) d
Results:
data
------------------------------ ---------------------------------------
1234 1234.000
1.23 1.230
abc NULL
.abc NULL
+6000 NULL
1.2.3 NULL
I.e. one additional check you may want to use is that a decimal cannot be the first or last character in the string. That is easy enough to do by adding those additional checks into the first CASE for the SingleDec column.
try ISNUMERIC function,
SELECT ISNUMERIC('abc')
SELECT ISNUMERIC('1.23')
On SQL Server (Version 2012, 11.0.5343)
SELECT
CASE WHEN ISNUMERIC('.') = 1 THEN <Field> ELSE 0 END
FROM
<Table>
works fine ...
Thre is A blog post.
Try following
SELECT
CASE
WHEN
ISNUMERIC(data + 'e0') = 1 THEN CAST(data AS decimal(18,2))
ELSE NULL END AS tData
FROM
DataTable
try ISNUMERIC function
DECLARE #MyTable TABLE(Val VARCHAR(100))
INSERT INTO #MyTable
VALUES
('1234')
,('1.23')
,('ABC')
,('.ABC')
,('MJA')
Select Val as OldValue,
Case
When ISNUMERIC(Val) = 1
then Cast(Val as numeric(18,2))
else null
end NewValue
From #MyTable
Output
OldValue NewValue
-----------------------------------------------------
1234 1234.00
1.23 1.23
ABC NULL
.ABC NULL
MJA NULL
(5 row(s) affected)

Why does this MySQL function return null?

Description:
the query actually run have 4 results returned,as can be see from below,
what I did is just concate the items then return,
but unexpectedly,it's null.
I think the code is self-explanatory:
DELIMITER |
DROP FUNCTION IF EXISTS get_idiscussion_ask|
CREATE FUNCTION get_idiscussion_ask(iask_id INT UNSIGNED) RETURNS TEXT DETERMINISTIC
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE body varchar(600);
DECLARE created DATETIME;
DECLARE anonymous TINYINT(1);
DECLARE screen_name varchar(64);
DECLARE result TEXT;
DECLARE cur1 CURSOR FOR SELECT body,created,anonymous,screen_name from idiscussion left join users on idiscussion.uid=users.id where idiscussion.iask_id=iask_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
SET result = '';
OPEN cur1;
REPEAT
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>');
UNTIL done END REPEAT;
CLOSE cur1;
RETURN result;
END |
DELIMITER ;
mysql> DELIMITER ;
mysql> select get_idiscussion_ask(1);
+------------------------+
| get_idiscussion_ask(1) |
+------------------------+
| NULL |
+------------------------+
1 row in set (0.01 sec)
mysql> SELECT body,created,anonymous,screen_name from idiscussion left join users on idiscussion.uid=users.id where idiscussion.iask_id=1;
+------+---------------------+-----------+-------------+
| body | created | anonymous | screen_name |
+------+---------------------+-----------+-------------+
| haha | 2009-05-27 04:57:51 | 0 | NULL |
| haha | 2009-05-27 04:57:52 | 0 | NULL |
| haha | 2009-05-27 04:57:52 | 0 | NULL |
| haha | 2009-05-27 04:57:53 | 0 | NULL |
+------+---------------------+-----------+-------------+
4 rows in set (0.00 sec)
For those who don't think the code is self-explanatory:
Why the function returns NULL?
Rename your variables and the input parameter, they're ambiguous.
This query:
SELECT body, created, anonymous, screen_name
FROM idiscussion
LEFT JOIN
users
ON idiscussion.uid = users.id
WHERE idiscussion.iask_id = iask_id
returns your previously declared variables (which are NULL), not the table columns.
Prepend the variable names and the input parameter name with an underscore.
Also you make an extra assignment to result:
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>');
The handler sets done after the FETCH fails, but the result gets assigned nevertheless.
Change your handler to:
DECLARE EXIT HANDLER FOR SQLSTATE '02000' RETURN result;
Finally: in MySQL, this can be done with a single query. There is no need to do it with a function.
SELECT GROUP_CONCAT(CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>') SEPARATOR '')
FROM idiscussion
LEFT JOIN
users
ON idiscussion.uid=users.id
WHERE idiscussion.iask_id = #_iask_id
Keep in mind that concatenating any string together with a NULL returns NULL. Try this test:
mysql> SET #s = 'test string';
mysql> SET #s = CONCAT(#s, '<tag>', NULL, '</tag>');
mysql> SELECT #s;
This returns NULL.
So as you loop through your cursor, if the body or created columns are NULL on any row, the result becomes NULL. Then on subsequent iterations of the loop anything concatenated with a NULL result has no effect; it stays NULL.
Try something like this:
REPEAT
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,
'<comment><body><![CDATA[',
COALESCE(body, ''),
']]></body>',
'<replier>',
IF(COALESCE(anonymous, 0) != 0, COALESCE(screen_name, ''), ''),
'</replier>',
'<created>',
COALESCE(created, ''),
'</created></comment>'
);
UNTIL done END REPEAT;
The COALESCE() function is a useful function in standard SQL. It returns its first non-NULL argument.
CONCAT_WS(separator, str1, str2,...)
CONCAT_WS() stands for CONCAT With Separator and is a special form of CONCAT(). The first argument is the separator for the rest of the arguments. The separator is added between the strings to be concatenated: The separator can be a string as can the rest of the arguments. If the separator is NULL, the result is NULL. The function skips any NULL values after the separator argument.
mysql>
SELECT CONCAT_WS(",","First name","Second name","Last Name");
-> 'First name,Second name,Last Name'
mysql>
SELECT CONCAT_WS(",","First name",NULL,"Last Name");
-> 'First name,Last Name'
Before MySQL 4.0.14, CONCAT_WS() skips empty strings as well as NULL values.
I try to be verbose because your question isn't ;)
You're expecting the return value of the function to be non-NULL since you're creating the return value by concatenating only non-NULL strings.
Only if one of the strings was NULL, the whole return value would be NULL. Your demo data only contains NULL values in screen_name, but you respected that case.
But somehow (atm I have no idea how) one of the values must be NULL and the relevant line to look at ist the one with the big CONCAT.
What if you shorten the relevant line for debug reasons to:
SET result = if(screen_name is not null,screen_name,'')
Does it still return NULL?