I need help in solving an issue in rounding decimals in T-SQL.
Amounts in my calculation: 7.34 & 6.16 - Column Datatype smallmoney
Percentages used in calculation: 56.25 & 43.75 - Column Datatype Decimal (5,2)
Using CAST((Amount * Percentage)/100) AS NUMERIC(36,2)) is having an issue for $6.16 because the total shows as 6.17 when rounded. I have tried other datatypes smallmoney, money, decimal and run into the same problem.
Sample code
-- Smallmoney and Decimal(5,2) are datatypes of the columns in the table
declare #amount smallmoney = 7.34
declare #amount2 smallmoney = 6.16
declare #percent1 decimal(5,2) = 56.25
declare #percent2 decimal(5,2) = 43.75
declare #total NUMERIC(36,2)
declare #total2 NUMERIC(36,2)
select cast((#amount * #percent1)/100 AS NUMERIC(36,2))
select cast((#amount * #percent2)/100 AS NUMERIC(36,2))
select cast((#amount * #percent1)/100 AS NUMERIC(36,2)) + cast((#amount * #percent2)/100 AS NUMERIC(36,2)) -- Total is 7.34. This is correct.
select cast((#amount2 * #percent1)/100 AS NUMERIC(36,2))
select cast((#amount2 * #percent2)/100 AS NUMERIC(36,2))
select cast((#amount2 * #percent1)/100 AS NUMERIC(36,2)) + cast((#amount2 * #percent2)/100 AS NUMERIC(36,2)) -- Total is 6.17. I need this to be 6.16
I need to produce a summary and detail reports. See sample below
drop table #percentconfig, #transdata, #detailreport
create table #percentconfig(Id int, Name varchar(30), perct decimal(5,2))
insert into #percentconfig values (1, '56 percent', 56.25)
insert into #percentconfig values (1, '43 percent', 43.75)
create table #transdata(transId int, LinkId int, Amount smallmoney)
insert into #transdata values (1, 1, 7.34)
insert into #transdata values (2, 1, 6.16)
select TransId, cast((t.Amount * p.perct)/100 AS NUMERIC(36,2)) as Amount
into #detailreport
from #percentconfig p
inner join #transdata t on p.Id = t.LinkId -- This is my query that produces the report.
select TransId, Amount from #transdata -- Summary Report
select TransId, Amount TotalAmount from #detailreport -- Detail Report
Any thoughts on how to approach this issue?
If you requirement is that all of the results have to add up to the original value (common for calculating taxes), you just can't round each value.
You need to look into rounding of previous calculation and round up or down depending on how previous rounding worked.
For calculation with 2 percentages following formula will work:
declare #amount2 smallmoney = 6.16
declare #percent1 decimal(5,2) = 56.25
declare #percent2 decimal(5,2) = 43.75
select cast((#amount2 * #percent1)/100 AS NUMERIC(36,2))
--select cast((#amount2 * #percent2)/100 AS NUMERIC(36,2))
select cast(#amount2 - cast((#amount2 * #percent1)/100 AS NUMERIC(36,2)) AS NUMERIC(36,2))
select cast((#amount2 * #percent1)/100 AS NUMERIC(36,2)) + cast(#amount2 - cast((#amount2 * #percent1)/100 AS NUMERIC(36,2)) AS NUMERIC(36,2)) -- The total here 6.17. I need 6.16
The question really is not about the datatypes, but about the rounding itself.
We can simplify your test case as something like this:
select cast(0.465 as numeric(36,2))
+ cast(0.535 as numeric(36,2))
The sum of the two original value is 1. However, summing then two rounded values does not guarantee that you get back the original value. The first cast returns 0.47, and the second one gives 0.54, so the sum evaluates as 1.01.
If you want a safe round trip, then sum first, then cast:
select cast(0.465 + 0.535 as numeric(36,2))
For your example query, that would be:
select cast(
#amount2 * #percent1 / 100 + #amount2 * #percent2 /100
as numeric(36,2)
)
There was a very similar question posted recently - see the question here SQL Server Decimal Operation is Not Accurate
and my answer here https://stackoverflow.com/a/64547227/14267425
Also note that DECIMAL and NUMERIC data types are the synonymous (e.g., decimal(5,2) is the same as numeric(5,2)) so it doesn't matter which you use.
The key thing is that decimal (including 'money' types) are exact in that once it calculates a value as, say, 6.66, then it treats at as exactly 6.66 further. This means, for example, that (20.0 / 3) * 3 potentially ends up as 19.999998 because it considers 20.0/3 = 6.666666 (depending on the number of decimal points it uses).
In your example, when you calculate
select cast((#amount2 * #percent1)/100 AS NUMERIC(36,2)) + cast((#amount2 * #percent2)/100 AS NUMERIC(36,2))
it calculates (and rounds to exact value) each of the components
then adds them together.
The simplest option here is to add them first, then round to the dollar values at the end e.g.,
select cast( ((#amount2 * #percent1)/100 + (#amount2 * #percent2)/100) AS NUMERIC(36,2))
However, you need to be really careful about when you do your conversions/rounding. For cash (e.g., money in hand) this is actually appropriate - you cannot give someone half a cent. However for banks and % transactions, this can be problematic.
I suggest trying float type variables for calculations where you want inexact values (like 6 and 2/3) then convert to decimal when you want to make it 'concrete'.
An alternative to using float values/datatypes, is to use a decimal with more decimal points (higher precision) than you need.
For example, if your data is in dollars and cents, you may do your calculations in decimal(18,4) so it has 4 decimal points - effectively doing calculations at 1/100th of a cent. Then when you make it 'concrete' as above, you round it to 2 decimal points e.g., decimal(18,2) representing exact dollars and cents.
Note that decimals with higher precision is occurring with your data set above e.g., the when doing the calculations with percentages, it adds precision to get more decimal points than what you need e.g., select (#amount2 * #percent1)/100 results in 3.4650000000
However, if you're doing it with a value with repeating decimals (e.g., splitting a $20.00 lunch bill across 3 people) the automatic precision updates won't cope and will round to a decimal. If you're only ever dividing by 100, this won't be an issue as it will just shift decimal places 2 places. However, if you want weekly averages (dividing by 52) or daily (dividing by 365 etc) it is quite likely you will run into some rounding issues.
Therefore, you need to be conscious of what type/precision of values are being used at what point in calculations, and control them to ensure they represent what they need to in real life.
The strategy is to use the higher precision decimals or floats for calculations you want to be take more detailed fractions into account, then round/convert the values when appropriate.
e.g.,
You're a shop, running a sale of 30% off on certain items (and working in dollars and cents)
You calculate the 30% off as float or high-precision decimal values, so you get the exact amounts rather than someone abusing it to get a few cents extra (or, alternatively, they don't get a big enough discount) when you get the total
When you get to the total, you convert it to decimal(18,2) so it's exact - as it represents the actual amount you are charging the customer. If you left it as a float, it may have a phantom fraction of a cent in the value, that creates an error when you total across all the day's sales.
I have changed my code as below based on Pitor and GMB suggestion. This gives the expected result.
drop table #percentconfig, #transdata, #detailreport, #detailreport2
create table #percentconfig(Id int, Name varchar(30), perct decimal(5,2))
insert into #percentconfig values (1, '56 percent', 56.25)
insert into #percentconfig values (1, '43 percent', 43.75)
create table #transdata(transId int, LinkId int, Amount smallmoney)
insert into #transdata values (1, 1, 7.34)
insert into #transdata values (2, 1, 6.16)
select TransId, t.Amount AS FullAmt, p.Perct as Percentage,
CASE WHEN (100 - p.perct) > p.perct THEN cast(t.Amount - cast((t.Amount * (100 - p.perct))/100 AS NUMERIC(36,2)) AS NUMERIC(36,2))
ELSE CAST(t.Amount * p.perct/100 AS NUMERIC(36,2)) END as Amount
into #detailreport
from #percentconfig p
inner join #transdata t on p.Id = t.LinkId -- This is my query that produces the report.
select * from #transdata -- Summary Report
select * from #detailreport order by transId-- Detail Report
I want to convert numeric value to Money but without Rounding value.W.r.t. to
Link : https://technet.microsoft.com/en-us/library/ms187928(v=sql.105).aspx It is rounding numeric to Money while casting.
But is it possible to give value upto 4 digit after decimal.
NUMERIC VALUE : 123456789.3333
MONEY VALUE OUTPUT required : 123,456,789.3333
May be you are looking for something like this
SELECT FORMAT(CONVERT(MONEY, CAST(123456789.3333 AS NUMERIC(18,4))), '###,###.####')
Result
123,456,789.3333
I guess you mean numerics where you have more than 4 digits, then you could use ROUND:
SELECT CAST(ROUND(123456789.33339, 4, 1) AS MONEY)
-- 123456789,3333
vs.
SELECT CAST(123456789.33339 AS MONEY)
-- 123456789,3334
Rextester Demo
if you wanna split number as 3 digit , you can use this code in your select command
Select LEFT(CONVERT(VARCHAR, CAST(YourPrice AS MONEY), 1), LEN(CONVERT(VARCHAR, CAST(UnitPrice AS MONEY), 1)) - 3 )as UnitPrice
I need a function which will always round up to nearest whole or half hour in SQL Server.
Ex.
1.2 = 1.5
1.5 = 1.5
1.6 = 2.0
1.0 = 1.0
0.2 = 0.5
0.8 = 1.0
I found many options on how to solve ALMOST this - but I would like one efficient one for this purpose specific.
Thanks..
If you are dealing with numbers, the simplest way is to multiply by 2, take the Ceiling (round up to nearest whole number), then divide by 2.
Select Ceiling(1.2 * 2) / 2
Select Ceiling(1.6 * 2) / 2
Since your question mentioned "whole or half hour", here's a bit of code that accommodates DateTime data:
Declare #Temp Table(Data DateTime)
Insert Into #Temp Values('20131114 11:00')
Insert Into #Temp Values('20131114 11:15')
Insert Into #Temp Values('20131114 11:30')
Insert Into #Temp Values('20131114 11:45')
Insert Into #Temp Values('20131114 11:59')
Select Data, DateAdd(Minute, Ceiling(DateDiff(Minute, '0:00', Data) / 30.0) * 30, '0:00')
From #Temp
I have table which has a column of float data type in SQL Server
I want to return my float datatype column value with 2 decimal places.
for ex: if i insert 12.3,it should return 12.30
if i insert 12,it should return 12.00
select cast(your_float_column as decimal(10,2))
from your_table
decimal(10,2) means you can have a decimal number with a maximal total precision of 10 digits. 2 of them after the decimal point and 8 before.
The biggest possible number would be 99999999.99
You can also do something much shorter:
SELECT FORMAT(2.3332232,'N2')
You can also use below code which helps me:
select convert(numeric(10,2), column_name) as Total from TABLE_NAME
where Total is alias of the field you want.
You can also Make use of the Following if you want to Cast and Round as well. That may help you or someone else.
SELECT CAST(ROUND(Column_Name, 2) AS DECIMAL(10,2), Name FROM Table_Name
select cast(56.66823 as decimal(10,2))
This returns 56.67.
Is there any function in Sql Server 2008 that controls scale numbers.For example
if we have a decimal number 123.456 and in function if pass 0 it should return 123, if i pass 1 then should return 123.4 if pass 2 then return 123.45 and if pass 3 then return 123.456.If there is no inbult function then Please let me know any user define function.Thanks, Ravi
ROUND() can truncate;
select round(123.456, 3, 1) union
select round(123.456, 2, 1) union
select round(123.456, 1, 1) union
select round(123.456, 0, 1)
>>123.456
>>123.450
>>123.400
>>123.000
If you don't want the trailing zeros remove them in the presentation layer, cast to a varchar or cast(round(123.456, 3, 1) as float)
CREATE FUNCTION f_test (
#a INT,
#b FLOAT
) RETURNS DECIMAL(12,6)
AS
BEGIN
RETURN CAST(#b * POWER(10, #a) AS INT)*1.0 / POWER(10, #a)
END
SELECT dbo.f_test(2, 123.456) AS RESULT
RESULT
----------
123.450000