Reputation: 3267
I'm storing a ratio of a total in a table and therefore know I'll only have values between 0 and 1, and 4 decimal places are sufficient precision. Therefore I've created the table column as DECIMAL(5,4)
.
When calculating the data for population I have found a strange behaviour where SQL seems to want an extra 2 places before the decimal, so if I use DECIMAL(5,4)
or DECIMAL(6,4)
I get an Arithmetic overflow error converting int to data type numeric.
error. I have to push it to DECIMAL(7,4)
for one side of the divide, then wrap the whole divide in another cast to get the precision I actually require.
SQL considers every different precision a different datatype, invoking a conversion, so I'm conscious of the workload this is adding when done at scale as I can't cast straight to the DECIMAL(5,4)
I'm storing it as. I'm also just curious as to what is happening to cause this.
The example below demonstrates the issue. It should work with DECIMAL(2,1)
, but it will only work with DECIMAL(3,1)
. Even then, the results are to 6 decimal places. Can anyone at least explain what's happening here, and hopefully a way to avoid it without doubling up on casts?
DECLARE @SalesTable TABLE (ProductId INT, Size NVARCHAR(2), Quantity INT)
INSERT INTO @SalesTable VALUES (123, 'S', 5), (123, 'M', 20), (123, 'L', 15), (123, 'XL', 10)
SELECT Size, sales.Quantity / CAST(sales.Total AS DECIMAL(3,1)) AS SalesRatio
FROM (
SELECT Size, Quantity, SUM([Quantity]) OVER (PARTITION BY ProductId) AS Total
FROM @SalesTable
) AS sales
uses an extra decimal and produces a result to a different precision
Size SalesRatio
S 0.100000
M 0.400000
L 0.300000
XL 0.200000
Upvotes: 0
Views: 1425
Reputation: 6015
The decimal precision should be set to the maximum length needed to contain the largest number included in the calculation (unless otherwise dealt with). In this case Quantity is data type INT which has min/max of -2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647). To be covered in all situations you could make the data type Decimal(16, 4). 12 digits for any integer + 4 places to the right of the decimal point. SQL Server with automatically expand the decimal precision if you add or multiply 1.0 to the denominator of a quotient. Something like this
DECLARE @SalesTable TABLE (ProductId INT, Size NVARCHAR(2), Quantity INT)
INSERT INTO @SalesTable VALUES (123, 'S', 5), (123, 'M', 20), (123, 'L', 15), (123, 'XL', 10)
SELECT Size, cast(sales.Quantity / (sales.Total*1.0) AS DECIMAL(16,4)) AS SalesRatio
FROM (
SELECT Size, Quantity, SUM([Quantity]) OVER (PARTITION BY ProductId) AS Total
FROM @SalesTable
) AS sales;
Output
Size SalesRatio
S 0.1000
M 0.4000
L 0.3000
XL 0.2000
Upvotes: 0
Reputation: 1269943
I don't really understand your confusion. In your example, salesTotal1
has a value of 50
. This requires two digits to the left of the decimal place.
Perhaps the confusion is on internal types for decimals. Although the rules are complicated for multiplication and very, very arcane for division, the rules are much simpler for addition: the type doesn't change.
So, the total needs to fit in the decimal value.
If you care about the type of the result of an arithmetic operation, then convert the entire expression:
SELECT Size,
CAST(sales.Quantity * 1.0 / sales.Total AS DECIMAL(3,1)) AS SalesRatio
The * 1.0
may not be necessary, but it makes sure that the division has decimal places.
Upvotes: 1