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'm trying to turn the results of the code below into decimals but I don't know how to or were to place a command such as "Convert" or "Decimal" in the code. Thank you in advance for your help.
select
system_type_id,
column_id,
system_type_id / column_id as calculation
from sys.all_columns
When I want to divide two integers and avoid integer division, I just multiple the numerator by 1.0:
select system_type_id, column_id, system_type_id * 1.0 / column_id as calculation
from sys.all_columns
I'm not sure why you would do this using ids from a system table, though.
select
convert(decimal(14,2),system_type_id),
convert(decimal(14,2),column_id),
convert(decimal(14,2),system_type_id / column_id) as calculation
from sys.all_columns
NB: the 2 means to 2 decimal place if you want to convert int to decimal
I want to get SUM divided by COUNT in a column
Select date,SUM(OOC) OOC,SUM(SUM_CVOICE) SUM, Count(SUM_CVOICE) Cnt,SUM(SUM_CVOICE)/Count(SUM_CVOICE) CVOICE
from #Temp
group by date
order by date
date OOC SUM Cnt CVOICE
03/01/2017 1569 19445 11252 1
04/01/2017 235 8299 4842 1
05/01/2017 154 11851 6361 1
It doesn't gives exact decimal values.
SQL Server does integer arithmetic. So, the simple answer is to convert your value to a non-integer in some way. A simple way is to multiply by * 1.0:
Select date, SUM(OOC) as OOC, SUM(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as cnt,
SUM(SUM_CVOICE)*1.0 / Count(SUM_CVOICE) as CVOICE
from #Temp
group by date
order by date;
However, what you are calculating is called the average. So, this is simpler:
Select date, sum(OOC) as OOC, sum(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as cnt,
avg(SUM_CVOICE * 1.0) as CVOICE
from #Temp
group by date
order by date;
Note that SQL Server also uses integer arithmetic for the average, so you need to convert to a non-integer as well.
disvision is tricky in SQL Server, you need to do implicit conversion, otherwise / will take the integer part of the result.
Select date,
SUM(OOC) as OOC,
SUM(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as Cnt,
SUM(SUM_CVOICE)*1.0/Count(SUM_CVOICE) as CVOICE
from #Temp
group by date
order by date
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 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)