Removing trailing zeros in case statement - sql

I've been trying to remove the trailing zeros from a column of a table. It works well when I try to remove the zeros from the column. However, when I use it with a case statement (to remove the zeros when a flag is turned on, and to keep them when a flag is turned off) it doesn't work properly. It doesn't recognize the flag. For example, I've hard coded the column as a constant value; while 1=0 (false), it is retrieving the value removing the zeros. It should be true in the else statement.
SELECT CASE WHEN 1=0 THEN cast(CAST(123.45000 AS decimal(6,2)) as float)
ELSE
'123.456700'
END
SELECT CASE WHEN 1=0 THEN CONVERT(DOUBLE PRECISION, 123.456700)
ELSE
'123.456700'
END
Why is this happening? Can anyone help me with this?
The above is well explained by #Tim below.
However, it doesn't remove the zeros at all in a table. It doesn't recognize the flag at all. Here is an example:
CREATE TABLE #tablea
(item CHAR(2), name VARCHAR(10), amount DECIMAL(9,2))
INSERT INTO #tablea
VALUES ('AB', 'D1', 1.10),
('AB', 'D2', 1.00),
('AB', 'D3', 0.90),
('AB', 'D4', 0.09)
DECLARE #flag INT = 1
SELECT CASE WHEN #flag = 1
THEN CAST(CAST(amount AS DECIMAL(6,2)) AS VARCHAR(max))
ELSE amount END
FROM #tablea

As #JNevill commented, what is happening here is that an implicit conversion is happening in the ELSE branch of your CASE expression, converting the string literal into a float, thereby removing the trailing zeroes when it gets printed. One option would be to cast the IF portion to VARCHAR:
SELECT
CASE WHEN 1=0
THEN CAST(CAST(123.45000 AS decimal(6,2)) AS varchar(max)) -- a string
ELSE '123.456700' END -- also a string
Demo
Note that in certain versions of SQL (other than yours) your CASE expression would not even run without error. It just so happens that a silent conversion is taking place here.

You can't remove trailing zeroes from a decimal data type. The decimal/numeric data types are fixed point data type:
Numeric data types that have fixed precision and scale. Decimal and numeric are synonyms and can be used interchangeably.
This means that the decimal point is in a fixed position within the stored number, unlike float and read which are floating point data types - so for a decimal(9,2) there will always be two digits to the right of the decimal point - and for numeric(5,3) where will always be three digits to the left of the decimal point.
If precision is not very important, you can convert to float - but you should be aware that unlike decimal, float is an approximate data type.
Please note that you would still have to convert both branches of the case statement to a varhcar otherwise SQL Server will implicitly convert both branches to float and it will look like the flag is being ignored.
DECLARE #flag INT = 1
SELECT
CASE WHEN #flag = 1
THEN CAST(CAST(amount AS float) as varchar(30))
ELSE CAST(amount as varchar(30))
END As [Remove trailing zeros],
-- This is to show the opposite branch
CASE WHEN #flag = 0
THEN CAST(CAST(amount AS float) as varchar(30))
ELSE CAST(amount as varchar(30))
END As [Include trailing zeros]
FROM #tablea
Results:
Remove trailing zeros Include trailing zeros
1.1 1.10
1 1.00
0.9 0.90
0.09 0.09

#Tim, thanks. It works in the demo you showed. But it doesn't remove the zeros at all in a table. It doesn't recognize the flag at all. Here is an example:
CREATE TABLE #tablea
(item CHAR(2), name VARCHAR(10), amount DECIMAL(9,2))
INSERT INTO #tablea
VALUES ('AB', 'D1', 1.10),
('AB', 'D2', 1.00),
('AB', 'D3', 0.90),
('AB', 'D4', 0.09)
DECLARE #flag INT = 1
SELECT CASE WHEN #flag = 1
THEN CAST(CAST(amount AS DECIMAL(6,2)) AS VARCHAR(max))
ELSE amount END
FROM #tablea

Related

Removing leading and trailing zeros and have the result in two decimal places

I wanted to remove both leading and trailing zeros, and want to have the result in two decimal places. For example: if the number is 0.01, I want to see .01; if the number is 0.010, I want to see .01; if the number is 0.10, I want to see .10; if the number is 1, I want to see 1.00.
I would use a FORMAT function.
For example:
SELECT FORMAT (0.090, '#.######')
works fine that it displays .09
However, when I use the same statement for a value greater than 1, it doesn't show the decimal places.
For example:
SELECT FORMAT (10, '#.######')
gives 10 while I'm expecting 10.00
I tried to use a CASE statement to solve this in vain. A case statement works fine for a value more than 1, but it doesn't remove the leading zeros.
DECLARE #a float = 10
SELECT
CASE
WHEN #a <1 THEN format (#a, '#.######')
ELSE CAST(FORMAT(#a, '#.######') AS money)
END
gives 10.00 as I expect. However, when the value of #a is 0.090 for example, it gives 0.09 while I expect .09
Is there any way I can remove both trailing and leading zeros and have the result in two decimal places? Any guide is much appreciated.
While I agree with D Stanley that 'Formatting is the responsibility of the display layer', I know sometimes you need some dirty functions to be used inside stored procedures or other functions. Try with this one:
CREATE FUNCTION dbo.toStr(#a float)
RETURNS varchar(12)
AS
BEGIN
DECLARE #ret varchar(12)
SELECT #ret =
CASE
WHEN #a < 1 THEN replace(format(#a, '.##'), '0.','.')
ELSE FORMAT(#a, '#.00')
END
RETURN #ret
END;
I have tested with the following values:
select dbo.toStr(0.090); -- .09
select dbo.toStr(1.09); -- 1.09
select dbo.toStr(10.00); -- 10.00
select dbo.toStr(1.2); -- 1.20

Variable precision in a column in SQL

I have a column which consists of three different types of numbers:
Type 1 has no digits after decimal point like 5, 17, etc.
Type 2 has one digit after decimal point like 27.5, 11.8, etc.
Type 3 has 2 digits after decimal points like 227.64, 35.77, etc.
I want the Type 1 numbers to have a 0 after decimal point so that they become 22.0, 11.0 and so on while the Type 2 and Type 3 numbers remain unaffected.
Can be done by pushing value into a string.
DECLARE #table AS TABLE (myValue FLOAT)
INSERT INTO #table
(myValue)
VALUES (5),
(17),
(27.5),
(11.8),
(227.64),
(35.77)
SELECT CASE WHEN CAST(myValue AS VARCHAR(20)) LIKE '%.%' THEN CAST(myValue AS VARCHAR(20))
ELSE CAST(myValue AS VARCHAR(20)) + '.0'
END
FROM #table
This is a poor solution at best. Your better maintaining your data in its original format and then handling your formatting in the presentation layer as already stated by Ankit. I know its not always possible, but the data place is not really the place to do this.
What is the data type of the column?
Try:
SELECT
CASE
WHEN LEN(RIGHT([MyColumn], CHARINDEX('.', REVERSE([MyColumn])))) >= 3
THEN [MyColumn]
ELSE CAST(CAST([MyColumn] AS NUMERIC(8,1)) AS NVARCHAR(10))
END AS [MyColumn]
FROM [MyTable]

Converting a mixed fraction to a float number in Netezza

I have a field with numbers stored as text in 3 formats:
xx. (example: 31.)
xx.x (example: 31.2)
xx x/x (example: 31 2/7)
For the final result, I need all numbers to be in decimal format (that is, xx.x).
Converting the first two formats into decimals is fairly simple, but I haven't quite figured out how to convert the last case, as a simple CAST function doesn't work. I've used the INSTR function to isolate all the fractional cases of these numbers, but I don't know where to go from there. I've looked at other examples but some of the functions referenced (like SUBSTRING_INDEX) don't exist in Netezza.
I think #Niederee has the solution from brute force, but I'd use the sql extensions toolkit.
create temporary table fractions (
val nvarchar(64)
) distribute on random;
insert into fractions values ('2.');
insert into fractions values ('2.3');
insert into fractions values ('31 2/7');
insert into fractions values('2 0/8');
insert into fractions values('516 56/537');
select
val
,case
when regexp_like(val,'^[\d\.]+$') then val::numeric(20,10) --Cast it if we can.
when regexp_like(val,'^[\d\.\s\/]+$')
then regexp_extract(val,'\d+',1,1)::numeric(20,10) --Whole.
+ (
regexp_extract(val,'\d+',1,2)::numeric(20,10) --Numerator.
/ regexp_extract(val,'\d+',1,3)::numeric(20,10) --Denominator.
)
else null
end
from
fractions;
Try the following:
create temp table so_test (
txt_val varchar(100)
);
insert into so_test values ('31.');
insert into so_test values ('31.2');
insert into so_test values ('31 2/7');
select txt_val
, cast(decode(substr(txt_val,1,instr(txt_val,' ')),'',txt_val,substr(txt_val,1,instr(txt_val,' '))) as numeric(18,2)) as root
,cast(substr(txt_val,instr(txt_val,' ')+1,length(txt_val)-instr(txt_val,'/')) as numeric(18,2))
/cast(substr(txt_val,instr(txt_val,'/')+1,length(txt_val)) as numeric(18,2)) as fraction
,cast(root + case when fraction = 1 then 0 else fraction end as numeric(3,1)) as num_val
from so_test
Thanks for the help everyone. I forgot to close this out, I actually figured out a way to do it:
select
case when instr(num,'/') > 0 then
cast(substr(num,1,2) as float)
+ (cast(substr(num,4,1) as float)/cast(substr(num,6,1) as float))
when instr(num,'.') > 0 then cast(substr(num,1,4) as float)
else cast(num as float)
end as float_num

Remove only zero after decimal sql server 2012

Consider the following numbers.
7870.2
8220.0
I need to remove decimal points if the value ends with .0. If it ends with .2 then it should keep the value as it is.
I have used ceiling but it removes all the values after decimal.
How can I write a select query in which I can add some condition for this?
Generally speaking you should not do this in your dB. This is an app or reporting side operation. The dB is made to store and query information. It is not made to format/string manipulate information.
use right within a case statement and:
DECLARE #val decimal(5,1)
SET #val = 7870.0
Select
Case
When right(#val,1)<> '0' then
cast(#val as varchar)
else
cast(cast(#val as int) as varchar)
End
output: 7870
EDIT: I could write :
Case
When right(#val,1)<> '0' then
#val
else
cast(#val as int) -- or floor(#val)
End
but because return type of case statement is the highest precedence type from the set of given types, so the output for second version is: 7870.0 not 7870, that's why I convert it to i.e varchar in when clauses, and it can be converted outside of case statement, I mean cast ((case when...then...else... end) as datatype)
Cast the number as a float, using float(24) to increase precision:
DECLARE #t table(number decimal(10,1))
INSERT #t values(7870.2),(8220.0)
SELECT cast(number as float(24))
FROM #t
Result:
7870,2
8220
Here below goes a sample:
declare #1 decimal(4,3)
select #1 = 2.9
select case when SUBSTRING (PARSENAME(#1,1), 1, 1) = 0 then FLOOR(#1) else #1 end
Change the #1 in the select statement with your database field name.
sqlfiddle
The solution seems to be simple:
SELECT CONVERT (FLOAT, PAYLOAD)

VARCHAR to DECIMAL

I want to convert a varchar(max) column to decimal(10,4).
When I try to use cast or convert I am getting an arithmetic overflow exception. The issue is that the data stored in the varchar column may contain different precisions and different scales. For example, 123456789.1234567', 1.12345678 or 123456.1234.
For values like 123456.1234 it is converting with out any issue but for other values I am having some problems.
After testing I found that it was not the decimal place that was causing the problem, it was the precision (10)
This doesn't work: Arithmetic overflow error converting varchar to data type numeric.
DECLARE #TestConvert VARCHAR(MAX) = '123456789.12343594'
SELECT CAST(#TestConvert AS DECIMAL(10, 4))
This worked
DECLARE #TestConvert VARCHAR(MAX) = '123456789.12343594'
SELECT CAST(#TestConvert AS DECIMAL(13, 4))
Should be like 9 int + 4 floating = 13 chars
My explanation is in the code. :)
DECLARE #TestConvert VARCHAR(MAX) = '123456789.1234567'
BEGIN TRY
SELECT CAST(#TestConvert AS DECIMAL(10, 4))
END TRY
BEGIN CATCH
SELECT 'The reason you get the message "' + ERROR_MESSAGE() + '" is because DECIMAL(10, 4) only allows for 4 numbers after the decimal.'
END CATCH
-- Here's one way to truncate the string to a castable value.
SELECT CAST(LEFT(#TestConvert, (CHARINDEX('.', #TestConvert, 1) + 4)) AS DECIMAL(14, 4))
-- If you noticed, I changed it to DECIMAL(14, 4) instead of DECIMAL(10, 4) That's because this number has 14 digits, as proven below.
-- Read this for a better explanation as to what precision, scale and length mean: http://msdn.microsoft.com/en-us/library/ms190476(v=sql.105).aspx
SELECT LEN(LEFT(#TestConvert, (CHARINDEX('.', #TestConvert, 1) + 4)))
I came up with the following solution:
SELECT [Str], DecimalParsed = CASE
WHEN ISNUMERIC([Str]) = 1 AND CHARINDEX('.', [Str])=0 AND LEN(REPLACE(REPLACE([Str], '-', ''), '+', '')) < 29 THEN CONVERT(decimal(38,10), [Str])
WHEN ISNUMERIC([Str]) = 1 AND (CHARINDEX('.', [Str])!=0 AND CHARINDEX('.', REPLACE(REPLACE([Str], '-', ''), '+', ''))<=29) THEN
CONVERT(decimal(38,10),
CASE WHEN LEN([Str]) - LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', '')) <= 38
THEN [Str]
ELSE SUBSTRING([Str], 1, 38 + LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', ''))) END)
ELSE NULL END
FROM TestStrToDecimal
I know it looks like an overkill and probably it is, but it works for me (checked both positive, negative, big and small numbers of different precision and scale - everything is converted to decimal(38,10) or NULL).
It is hard-coded to decimal(38,10) type, so if you need different precision, change the constants in the code (38, 10, 29).
How it works? The result is:
if conversion is simple without overflow or precision loss (e.g. 123 or 123.456), then it just convert it.
if number is not too big, but has too many digits after decimal point (e.g. 123.1234567890123456789012345678901234567890), then it trims the exceeding digits at the end keeping only 38 first digits.
if number is too big and can't be converted to decimal without an overflow (e.g. 9876543210987654321098765432109876543210), then NULL is returned
each case is separate WHEN statement inthe code above.
Here are few examples of conversion:
You still haven't explained why you can't use a Float data type, so here is an example:
DECLARE #StringVal varchar(50)
SET #StringVal = '123456789.1234567'
SELECT #StringVal, CAST(#StringVal AS FLOAT)
SET #StringVal = '1.12345678'
SELECT #StringVal, CAST(#StringVal AS FLOAT)
SET #StringVal = '123456.1234'
SELECT #StringVal, CAST(#StringVal AS FLOAT)
You are missing the fact that 6.999,50 is not a valid decimal. You can't have a comma and a decimal point in a decimal value surely? What number is it supposed to be?
Assuming your locale specifies . as grouping and , as decimal separator: To remove the grouping digits:
SELECT CONVERT(decimal(11,2), REPLACE('6.999,50', '.', ''))
will yield 6999,50 as a decimal
You are going to have to truncate the values yourself as strings before you put them into that column.
Otherwise, if you want more decimal places, you will need to change your declaration of the decimal column.
Your major problem is not the stuff to the right of the decimal, it is the stuff to the left. The two values in your type declaration are precision and scale.
From MSDN: "Precision is the number of digits in a number. Scale is
the number of digits to the right of the decimal point in a number.
For example, the number 123.45 has a precision of 5 and a scale of 2."
If you specify (10, 4), that means you can only store 6 digits to the left of the decimal, or a max number of 999999.9999. Anything bigger than that will cause an overflow.
Implemented using Custom Function.
This will check whether the string value can be converted to Decimal safely
CREATE FUNCTION [dbo].[TryParseAsDecimal]
(
#Value NVARCHAR(4000)
,#Precision INT
,#Scale INT
)
RETURNS BIT
AS
BEGIN
IF(ISNUMERIC(#Value) =0) BEGIN
RETURN CAST(0 AS BIT)
END
SELECT #Value = REPLACE(#Value,',','') --Removes the comma
--This function validates only the first part eg '1234567.8901111111'
--It validates only the values before the '.' ie '1234567.'
DECLARE #Index INT
DECLARE #Part1Length INT
DECLARE #Part1 VARCHAR(4000)
SELECT #Index = CHARINDEX('.', #Value, 0)
IF (#Index>0) BEGIN
--If decimal places, extract the left part only and cast it to avoid leading zeros (eg.'0000000001' => '1')
SELECT #Part1 =LEFT(#Value, #Index-1);
SELECT #Part1=SUBSTRING(#Part1, PATINDEX('%[^0]%', #Part1+'.'), LEN(#Part1));
SELECT #Part1Length = LEN(#Part1);
END
ELSE BEGIN
SELECT #Part1 =CAST(#Value AS DECIMAL);
SELECT #Part1Length= LEN(#Part1)
END
IF (#Part1Length > (#Precision-#Scale)) BEGIN
RETURN CAST(0 AS BIT)
END
RETURN CAST(1 AS BIT)
END
I know this is an old question, but Bill seems to be the only one that has actually "Explained" the issue. Everyone else seems to be coming up with complex solutions to a misuse of a declaration.
"The two values in your type declaration are precision and scale."
...
"If you specify (10, 4), that means you can only store 6 digits to the
left of the decimal, or a max number of 999999.9999. Anything bigger
than that will cause an overflow."
So if you declare DECIMAL(10,4) you can have a total of 10 numbers, with 4 of them coming AFTER the decimal point.
so 123456.1234 has 10 digits, 4 after the decimal point. That will fit into the parameters of DECIMAL(10,4).
1234567.1234 will throw an error. there are 11 digits to fit into a 10 digit space, and 4 digits MUST be used AFTER the decimal point. Trimming a digit off the left side of the decimal is not an option.
If your 11 characters were 123456.12345, this would not throw an error as trimming(Rounding) from the end of a decimal value is acceptable.
When declaring decimals, always try to declare the maximum that your column will realistically use and the maximum number of decimal places you want to see.
So if your column would only ever show values with a maximum of 1 million and you only care about the first two decimal places, declare as DECIMAL(9,2).
This will give you a maximum number of 9,999,999.99 before an error is thrown.
Understanding the issue before you try to fix it, will ensure you choose the right fix for your situation, and help you to understand the reason why the fix is needed / works.
Again, i know i'm five years late to the party.
However, my two cents on a solution for this, (judging by your comments that the column is already set as DECIMAL(10,4) and cant be changed)
Easiest way to do it would be two steps.
Check that your decimal is not further than 10 points away, then trim to 10 digits.
CASE WHEN CHARINDEX('.',CONVERT(VARCHAR(50),[columnName]))>10 THEN 'DealWithIt'
ELSE LEFT(CONVERT(VARCHAR(50),[columnName]),10)
END AS [10PointDecimalString]
The reason i left this as a string is so you can deal with the values that are over 10 digits long on the left of the decimal.
But its a start.
create function [Sistema].[fParseDecimal]
(
#Valor nvarchar(4000)
)
returns decimal(18, 4) as begin
declare #Valores table (Valor varchar(50));
insert into #Valores values (#Valor);
declare #Resultado decimal(18, 4) = (select top 1
cast('' as xml).value('sql:column("Valor") cast as xs:decimal ?', 'decimal(18, 4)')
from #Valores);
return #Resultado;
END
In case you need to ROUND the result, not truncate, can use this:
select convert(decimal(38,4), round(convert(decimal(38,10), '123456789.1234567'),4))
This will return the following:
'123456789.1235' for '123456789.1234567'
'123456789.1234' for '123456789.1234467'
In MySQL
select convert( if( listPrice REGEXP '^[0-9]+$', listPrice, '0' ), DECIMAL(15, 3) ) from MyProduct WHERE 1