Divide By Zero - Can't see why this would fail - sql

I have written a function just to work out the Pro-Rata Clawback on any amount me typing it out constantly.
However I get a Divide By Zero Error everytime it runs, I broke the function down very simply.
DECLARE #ProRataAmount DECIMAL(8,2), #DaysRemaining INT,#NetValue DECIMAL(8,2), #TimeOnRisk INTEGER
SET #NetValue = 29.00
SET #TimeOnRisk = 8
SET #DaysRemaining = (365 - #TimeOnRisk)
PRINT #DaysRemaining
PRINT #NetValue
SELECT #NetValue / (#DaysRemaining/365)
There is NO value there that is dividing by zero, so why is it returning said error?
Cheers.

An INT/INT will return an INT.
Try the following (notice the 365.0):
SELECT #NetValue / (#DaysRemaining/365.0)
Now, just in case #DaysRemaining is ZERO, you can still generate a Divide By Zero unless you trap it like so:
SELECT #NetValue / (NullIf(#DaysRemaining,0)/365.0)

Dividing an integer with an integer will cause the problem here:
SELECT #NetValue / (#DaysRemaining/365)
Here you will always divide by zero because #DaysRemaining is less than the divisor. You can change the calculation around to get past this easily while keeping the value as integer:
SELECT #NetValue * 365 / #DaysRemaining

Related

SQL Server retain decimal scale and precision for operations on very lage numbers

Following my previous question I am still struggling with an arithmetic overflow error in SQL Server 2017. How can I perform a multiplication/division of a large decimal(38,0) value and retain/regain both the precision and scale?
In my current example I simply want to halve DECLARE #var1 decimal(38,0) = 85070591730234615865699536669866196992; (and truncate the result if #var1 were odd). From this post I have gathered that even if I do not attempt to divide by 2 but multiply by 0.5 instead I would still not obtain a decimal(38,0) result. I could divide by 2,000,000 to make the result fit the resulting decimal(38,6) type, but considering the multiplication rules etc. I can't figure out how to get back to decimal(38,0).
This answer is specific to your specific question -- dividing by 2. If you want a more general solution, please ask a new question.
Dividing by 2 is the same as multiplying by 5 and dividing by 10.
We cannot multiply your value by 5, but we can do the following:
Integer divide by 10.
Multiply by 5.
Add in a little bit extra to account for the missing digit.
But . . . you are thinking . . . how can we divide by 10? Well, you can do that with string operations: just truncate the last digit:
declare #var1 decimal(38,0) = 85070591730234615865699536669866196992;
select (convert(decimal(38,0), left(convert(varchar(38), #var1), 37)) * 5 +
convert(decimal(38, 0), (#var1 % 10) / 2)
) as half_a_big_value
Here is a db<>fiddle.

SQL Percent Error. Trouble with trying to use ABS()

I am trying to find the Percent error of two columns for each row.
Currently I tried
UPDATE Weather
SET PercentError=ActualTemp - ForecastTemp / ActualTemp * 100
Which I know is in correct because when i do the calculations it doesn't match up with what the sql gives me. I then tried to use something along the lines of
UPDATE Weather
SET PercentError=ABS (ActualTemp - ForecastTemp) / ActualTemp * 100
But when i do this I just get 0 for my Percent error. I used ABS because I know it works with an INT but wanted to see if it would work when subtracting two columns.
I have been looking up how to subtract two columns using abs but they just use ABS to turn their number into positive and never use it in the equation itself. Is anyone able to point me in the right direction on how to get this to work correctly?
*Using Microsoft sql server
It's doing integer calculations. Try floating point math:
UPDATE Weather
SET PercentError=
(100.0 * ABS (ActualTemp - ForecastTemp)) / ActualTemp
Note that I placed the 100 in front of the equation, and forced the multiplication before the division.
How bout fixing the parentheses?
UPDATE Weather
SET PercentError = (ActualTemp - ForecastTemp) * 100.0 / ActualTemp ;
The 100.0 ensures that the division is not integer division.
Negative numbers seem reasonable, but you can include ABS():
UPDATE Weather
SET PercentError = ABS(ActualTemp - ForecastTemp) * 100.0 / ActualTemp ;

How to get exact value in SQL Server

I am dividing 100000=Amount into 9=Shift into Days=30
But when I need the actual amount so how do I get it?
In SQL:
Declare #R1 as money, #R2 as money, #R3 as money
Set #R1 = 100000 / 9 / 30
Set #R2 = #R1 * 9 * 30
select #R2 As 'ActualAmount'
I need exactly 100,000, but the result is 99900.00 - why?
By default sql will do integer division. Just add *1.0 to make it decimal division
Set #R1=100000*1.0/9/30
SQL DEMO
Use float instead of money
SELECT (100000/9/30)*9*30 result, 'v1' as version
UNION
SELECT (100000*1.0/9/30)*9*30, 'v2'
UNION
SELECT (CAST(100000 as float)/9/30)*9*30, 'v3'
ORDER BY version
OUTPUT
Welcome to the wonderful world of floating point rounding errors. 100000 / 9 = 11111.1111... repeating. Let's simplify the example and pretend we just want 10 / 3; different function, same problem. When 10 / 3 is expressed as a fraction, you could do (10 / 3) * 3, cancel out the threes, and get back to 10. However a computer stores 10 / 3 as 3.33333... repeating, out to as high a precision as it can get. What that precision is, is actually a somewhat complicated question to answer, but suffice it to say, it's not infinity. So what actually gets stored is something like 3.333333....0. When you multiply that by 3, the computer says "ah, you asked me to multiply 3.3333...0 * 3, which is of course 9.99999...0".
Mathematically, your expectation to get 10 back (or in your case, 100,000) is completely reasonable. But because of how a computer stores numbers with repeating decimals, the computer doesn't recognize there's an equivalence between 10 / 3 and 3.33333... (Actually, it sort of does, but the reason it does is probably more to do with luck; more on this later).
As was pointed out in another answer, the reason you're getting 99900 is because it's doing integer division rather than floating point division. So the question then becomes what do you cast 100000 as to get the answer you want?
In your case, float seems to get it right, although it's worth noting that this may not be the case in all circumstances. This probably turns out right because float has 53 decimal places available in its mantissa (significant digits) and with that level of precision, SQL makes an educated guess that you were, in fact, trying to re-form a fraction with infinitely repeating decimal places rather than multiply three numbers very close to, but not quite equal to the original number.
Money on the other hand is usually a bad choice as a data type because it's effectively just a small precision float and leads to problems like this. Decimal also has some problems in this case, as it will actually overshoot the re-formed value.
declare
#Money money = cast(100000 as money) / 2700,
#Float float = cast(100000 as float) / 2700,
#Decimal decimal(38,2) = cast(100000 as decimal(38, 2)) / 2700
select
_Money = #Money,
_MoneyReformed = #Money * 2700,
_Float = #Float,
_FloatReformed = #Float * 2700,
_Decimal = #Decimal,
_DecimalReformed = #Decimal * 2700
If you need a true, universally correct representation of fractions, you'll have to store the numerator and denominator in separate columns so you can manipulate each independently.

How to reduce the float length

Using SQL Server 2000
I want to reduce the decimal length
Query
Select 23/12 as total
Output is showing as 1.99999999999
I don't want to round the value, I want to diplay like this 1.99
Tried Query
Select LEFT(23/12, LEN(23/12) - 3) as total
The above query is working only if there is decimal value like 12.444444, but if the total is single digit means like 12 or 4 or 11...., i am getting error at run time.
How to do this.
Need Query Help
There is a very simple solution. You can find it in BOL. Round takes an optional 3rd argument, which is round type. The values are round or truncate.
ROUND numeric_expression , length [ ,function ] )
...
function Is the type of operation to perform. function must be
tinyint, smallint, or int. When function is omitted or has a value of
0 (default), numeric_expression is rounded. When a value other than 0
is specified, numeric_expression is truncated.
So just do
Select ROUND(cast(23 as float)/12, 2, 1) as total
That gives 1.91. Note, if you were really seeing 1.999 - something is really wrong with your computer. 23/12 = 1.916666666(ad infinitum). You need to cast one of the numbers as float since sql is assuming they're integers and doing integer division otherwise. You can of course cast them both as float, but as long as one is float the other will be converted too.
Not terribly elegant, but works for all cases:
Select CONVERT(float,LEFT(CONVERT(nvarchar, 23.0/12.0),CHARINDEX('.',CONVERT(nvarchar, 23.0/12.0)) + 2)) as total
Scalar Function
-- Description: Truncate instead of rounding a float
-- SELECT dbo.TruncateNumber(23.0/12.0,2)
-- =============================================
CREATE FUNCTION TruncateNumber
(
-- Add the parameters for the function here
#inFloat float,
#numDecimals smallint
)
RETURNS float
AS
BEGIN
IF (#numDecimals < 0)
BEGIN
SET #numDecimals = 0
END
-- Declare the return variable here
RETURN CONVERT(float,LEFT(CONVERT(nvarchar, #inFloat),CHARINDEX('.',CONVERT(nvarchar, #inFloat)) + #numDecimals))
END
GO

SQL Random number not working

declare #fieldForceCounter as int
declare #SaleDate as dateTime
declare #RandomNoSeed as decimal
set #fieldForceCounter = 1
set #SaleDate = '1 Jan 2009'
set #RandomNoSeed = 0.0
WHILE #fieldForceCounter <= 3
BEGIN
while #SaleDate <= '1 Dec 2009'
begin
INSERT INTO MonthlySales(FFCode, SaleDate, SaleValue) VALUES(#fieldForceCounter, #SaleDate, RAND(#RandomNoSeed))
set #saleDate = #saleDate + 1
set #RandomNoSeed = Rand(#RandomNoSeed) + 1
end
set #SaleDate = '1 Jan 2009'
set #fieldForceCounter = #fieldForceCounter + 1
END
GO
This T-SQL command was supposed to insert random values in the 'SaleValue'-column in the 'MonthlySales'-table.
But it is inserting '1' every time .
What can be the problem?
Two problems:
Firstly, the rand() function returns a number between 0 and 1.
Secondly, when rand() is called multiple times in the same query (e.g. for multiple rows in an update statement), it usually returns the same number (which I suspect your algorithm above is trying to solve, by splitting it into multiple calls)
My favourite way around the second problem is to use a function that's guaranteed to return a unique value each time, like newid(), convert it to varbinary, and use it as the seed :)
Edit: after some testing, it seems you'll need to try using a different datatype for #RandomNoSeed; float behaves somewhat different to decimal, but still approaches a fixed value, so I'd recommend avoiding the use of #RandomNoSeed altogether, and simply use:
INSERT INTO MonthlySales(FFCode, SaleDate, SaleValue)
VALUES(#fieldForceCounter, #SaleDate, RAND(convert(varbinary,newid())))
You have major issues here...
Decimal issues
The default precision/scale for decimal is 38,0. So you aren't storing any decimal part.
So you are only using RAND(0) for 1st iteration and RAND(1) for all subsequent iterations, which is 0.943597390424144 and 0.713591993212924
I can't recall how rounding/truncation applies, and I don't know what datatype SalesValue is, but rounding would give "1" every time.
Now, if you fix this and declare decimal correctly...
Seeding issues
RAND takes an integer seed. Seeding with 1.0001 or 1.3 or 1.999 gives the same value (0.713591993212924).
So, "Rand(1.713591993212924) + 1" = "RAND(1) + 1" = "1.713591993212924" for every subsequent iteration.
Back to square one...
To fix
Get rid of #RandomNoSeed
Either: Generate a random integer value using CHECKSUM(NEWID())
Or: generate a random float value using RAND() * CHECKSUM(NEWID()) (Don't care about seed now)
Just a guess, but often rand functions generate a number from 0-1. Try multiplying your random number by 10.