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
Related
I am trying to round off a result in a query (1 column out of many)
Select * (DocTotal - Vat) * Rate AS TOTAL, ItemCode, Qty
From
ORDR
above is my select statement (from within a bigger view)
Doctotal is a decimal 20,2
Vat is a decimal 20,2
and
Rate is decimal 2,4
(The other columns aren't in question)
this returns a result as TOTAL with 6 decimal places, eg 353.690000
is it possible to round the result that is calculated to 2 decimal places.
Casting it to a decimal will implicitly round the resulting float value from the calculation.
select *,
cast((DocTotal - Vat) * Rate as decimal(20,2)) AS TOTAL,
ItemCode, Qty
from ORDR
So first rounding it with ROUND isn't actually required.
Or you could use CONVERT instead, if you prefere that syntax.
Works the same way in this case. But it's specific to SQL Server.
While CAST is a standard feature (SQL-92).
convert(decimal(20,2), (DocTotal - Vat) * Rate) AS TOTAL
I have following statement
select pkid
from AttendancePosting
where datename(dw,AttDate) = 'Sunday' and empid=4 and attdate='2015-12-13'
group by PKId,timeout
--having 9=9
having cast(sum((datepart(minute, timeout)))/2 as float )+''=cast(datepart(minute,timeout) as float) +''
The problem is
having cast(sum((datepart(minute, timeout)))/2 as float )+''=cast(datepart(minute,timeout) as float) +''
Not working. both cast(sum((datepart(minute, timeout)))/2 as float ) and cast(datepart(minute,timeout) as float) bring the same value but still the select statement is not fetching any records, both returns 9
I have checked it like this
select pkid
from AttendancePosting
where datename(dw,AttDate) = 'Sunday' and empid=4 and attdate='2015-12-13'
group by PKId,timeout
having 9=9
And its bringing records, Any help will be appreciated.
First, your statement datepart(minute, timeout)/2 is going to return an integer. You can make SQL Server get more precise by being more precise like this datepart(minute, timeout)/2..
Second, floating point numbers are an approximation. You would do better to use ROUND() and specify the number of decimal places you think is appropriate. For example: round(sum((datepart(minute, timeout)))/2.0, 3).
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 this number 0.581183781439, I need to round the number 0.582
I was trying with
SELECT ROUND (0.581183781439, 3)
-------------------------------
0.581000000000
Is it useful ROUND function for this?
DECLARE #Test TABLE (Col NUMERIC(38,12));
INSERT #Test (Col)
SELECT 0.581183781439
UNION ALL
SELECT 0.5815
UNION ALL
SELECT 0.581883781439
UNION ALL
SELECT -0.581883781439;
SELECT Col AS [Col],
ROUND(Col, 3) AS StandardRounding_3decimals,
ROUND(Col, 3, 1) AS Truncation_3decimals,
FLOOR(Col*1000)/1000 AS RoundDown_3decimals,
CEILING(Col*1000)/1000 AS RoundUp_3decimals
FROM #Test;
Results:
Col StandardRounding_3decimals Truncation_3decimals RoundDown_3decimals RoundUp_3decimals
--------------- -------------------------- -------------------- ------------------- -----------------
0.581183781439 0.581000000000 0.581000000000 0.581000 0.582000
0.581500000000 0.582000000000 0.581000000000 0.581000 0.582000
0.581883781439 0.582000000000 0.581000000000 0.581000 0.582000
-0.581883781439 -0.582000000000 -0.581000000000 -0.582000 -0.581000
Maybe you need this?
SELECT ROUND(0.581183781439, 3,1) + .001
but correct rounding is 0.581.
We can write a T-SQL function to round up for an arbitrary number of decimal places.
CREATE FUNCTION RoundUp (#value float, #places int) RETURNS float
AS
BEGIN
RETURN SELECT CEILING(#value * POWER(10, #places)) / POWER(10, #places)
END
GO
SELECT Result = dbo.RoundUp(0.581183781439, 3)
Result
0.582
Adding a half value and using normal round will get the correct result.
SELECT ROUND (0.581183781439 + .0005, 3)
Bonus: Subtract the half value instead, and it will round down.
EDIT: Actually, this has the same flaw as an answer above when the value is zeros after the 3rd decimal.
I am trying to run this query, but it seems like it's not formatting the numbers properly after doing a mathematical calculation. The scale should be at 2, but it won't display as it should.
SELECT 30 / 60 as Diff FROM Table
This returns as 0
SELECT Convert( Numeric(8,2), 30 / 60 ) as Diff FROM Table
This returns as 0.00
How do I get this to return 0.50 as needed?
You can try the following and there are few ways to achieve it. Using a variable, or just performing CAST, CONVERT on the fields itself.
SELECT (CAST(30 AS DECIMAL(8,2)) / CAST(60 AS DECIMAL(8,2))) as Diff FROM Table
SELECT (CAST(30/60) AS DECIMAL(8,2)) as Diff FROM Table
SELECT (30/60.0) as Diff FROM Table;
SELECT CONVERT(DECIMAL(8,2), 30/60.0) as Diff FROM Table
Please take a look at this MSDN article for further reference:
Convert to numeric before doing the division or divide real numbers instead of integers. When doing integer arithmetic any decimal fraction is dropped. Converting after the fraction has been dropped doesn't do you any good.
SELECT CONVERT(NUMERIC(8,2),30.0/60.0) AS DIFF FROM TABLE
or if selecting columns instead of using numbers
SELECT CONVERT(NUMERIC(8,2),columnA)/CONVERT(NUMERIC(8,2),columnB) AS DIFF FROM TABLE
(converting both just to be sure the division is done according to the rules we want)