SQL Scalar-Valued Function - sql-server

I am looking to retrieve a value of the profit (FilmBoxOfficeDollar - FilmBudgetDollars) based on the Studio given as a parameter to the function.
USE Movies;
GO
CREATE FUNCTION fnmovieProfits(#StudioName nvarchar(255))
RETURNS int
AS
BEGIN
RETURN (SELECT SUM(FilmBoxOfficeDollars - FilmBudgetDollars)
FROM Film JOIN Studio
ON Film.FilmStudioID = Studio.StudioID
WHERE StudioName = #StudioName);
END;
GO
SELECT [dbo].[fnmovieProfits]('Dreamworks');
Whenever I run this through to pull the piece of data I get the following error:
Msg 8115, Level 16, State 2, Line 13
Arithmetic overflow error converting expression to data type int.
Any help would be much appreciated!

The problem you are experiencing is that you are overflowing the allowed value of a 32 bit number (INT); if you cast/convert to a 64 bit number (BIGINT) and return that datatype, the issue will be corrected. Proof of concept showing the issue:
DECLARE #BigNumber INT=2000000000
select CONVERT(BIGINT,#BigNumber) + CONVERT(BIGINT,#BigNumber) --returns 4,000,000,000
select (#BigNumber + #BigNumber) --errors with "Arithmetic overflow error converting expression to data type int."
BUT, do yourself a favor and use a view instead. Scalars like that are terrible for performance in reports. Scalar functions should never be used unless they are simply doing calculations based on input values (i.e. not hitting underlying, persisted data).
CREATE VIEW dbo.v_StudioProfits
AS
SELECT
StudioName,
SUM(CONVERT(BIGINT,FilmBoxOfficeDollars) - CONVERT(BIGINT,FilmBudgetDollars)) AS [Profit]
FROM Film
INNER JOIN Studio ON Film.FilmStudioID = Studio.StudioID
GROUP BY StudioName
GO
SELECT * FROM dbo.v_StudioProfits WHERE StudioName='Dreamworks'
Relevant reading on SQL Server datatypes. Specifically, integer datatypes.

Your sum is exceeding int range. You should define your return type as bigint:
CREATE FUNCTION fnmovieProfits(#StudioName nvarchar(255))
RETURNS bigint
AS.......
The maximum value you can return with int as a return type is 2147483647. Your sum is probably bigger than that.
One example of function that exceeds its return type:
CREATE FUNCTION testFunction()
RETURNS int
AS
BEGIN
RETURN (SELECT 2147483647 + 1);
END;
GO
SELECT [dbo].[testFunction]();
If you execute it you will get the following error:
Msg 8115, Level 16, State 2, Line 8
Arithmetic overflow error converting expression to data type int.
So the solution is just to increase your return type range by replacing int with bigint.

Related

SQL Server CHOOSE() function behaving unexpectedly with RAND() function

I've encountered an interesting SQL server behaviour while trying to generate random values in T-sql using RAND and CHOOSE functions.
My goal was to try to return one of two given values using RAND() as rng. Pretty easy right?
For those of you who don't know it, CHOOSE function accepts in an index number(int) along with a collection of values and returns a value at specified index. Pretty straightforward.
At first attempt my SQL looked like this:
select choose(ceiling((rand()*2)) ,'a','b')
To my surprise, this expression returned one of three values: null, 'a' or 'b'. Since I didn't expect the null value i started digging. RAND() function returns a float in range from 0(included) to 1 (excluded). Since I'm multiplying it by 2, it should return values anywhere in range from 0(included) to 2 (excluded). Therefore after use of CEILING function final value should be one of: 0,1,2. After realising that i extended the value list by 'c' to check whether that'd be perhaps returned. I also checked the docs page of CEILING and learnt that:
Return values have the same type as numeric_expression.
I assumed the CEILINGfunction returned int, but in this case would mean that the value is implicitly cast to int before being used in CHOOSE, which sure enough is stated on the docs page:
If the provided index value has a numeric data type other than int,
then the value is implicitly converted to an integer.
Just in case I added an explicit cast. My SQL query looks like this now:
select choose(cast(ceiling((rand()*2)) as int) ,'a','b','c')
However, the result set didn't change. To check which values cause the problem I tried generating the value beforehand and selecting it alongside the CHOOSE result. It looked like this:
declare #int int = cast(ceiling((rand()*2)) as int)
select #int,choose( #int,'a','b','c')
Interestingly enough, now the result set changed to (1,a), (2,b) which was my original goal. After delving deeper in the CHOOSE docs page and some testing i learned that 'null' is returned in one of two cases:
Given index is a null
Given index is out of range
In this case that would mean that index value when generated inside the SELECT statement is either 0 or above 2/3 (I'm assuming that negative numbers are not possible here and CHOOSE function indexes from 1). As I've stated before 0 should be one of possibilities of:
ceiling((rand()*2))
,but for some reason it's never 0 (at least when i tried it 1 million+ times like this)
set nocount on
declare #test table(ceiling_rand int)
declare #counter int = 0
while #counter<1000000
begin
insert into #test
select ceiling((rand()*2))
set #counter=#counter+1
end
select distinct ceiling_rand from #test
Therefore I assume that the value generated in SELECT is greater than 2/3 or NULL. Why would it be like this only when generated in SELECT statement? Perhaps order of resolving CAST, CELING or RAND inside SELECT is different than it would seem? It's true I've only tried it a limited number of times, but at this point the chances of it being a statistical fluctuation are extremely small. Is it somehow a floating-point error? I truly am stumbled and looking forward to any explanation.
TL;DR: When generating a random number inside a SELECT statement result set of possible values is different then when it's generated before the SELECT statement.
Cheers,
NFSU
EDIT: Formatting
You can see what's going on if you look at the execution plan.
SET SHOWPLAN_TEXT ON
GO
SELECT (select choose(ceiling((rand()*2)) ,'a','b'))
Returns
|--Constant Scan(VALUES:((CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(1) THEN 'a' ELSE CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(2) THEN 'b' ELSE NULL END END)))
The CHOOSE is expanded out to
SELECT CASE
WHEN ceiling(( rand() * 2 )) = 1 THEN 'a'
ELSE
CASE
WHEN ceiling(( rand() * 2 )) = 2 THEN 'b'
ELSE NULL
END
END
and rand() is referenced twice. Each evaluation can return a different result.
You will get the same problem with the below rewrite being expanded out too
SELECT CASE ceiling(( rand() * 2 ))
WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
Avoid CASE for this and any of its variants.
One method would be
SELECT JSON_VALUE ( '["a", "b"]' , CONCAT('$[', FLOOR(rand()*2) ,']') )

On Insert: Arithmetic overflow error converting expression to data type int

I tried creating my third table of phone numbers, I wrote the following commands:
CREATE TABLE MYPHONE (EMPLOYEE_ID INT, PHONE_NUMBER INT)
INSERT INTO MYPHONE
VALUES (1 , 7894561230)
But when I tried executing this previous INSERT command, I got an error:
Msg 8115, Level 16, State 2, Line 46
Arithmetic overflow error converting expression to data type int.
An int probably isn't the best data type for a phone number - you don't need to do any arithmetic on phone numbers, so why use a numerical data type? Store it as a string (or varchar in SQL).
Also, if you did need to store a value as an int, 2,147,483,647 is the maximum - anything higher would "overflow", hence the error you're getting. A long (or bigint in SQL) would allow values up to 9,223,372,036,854,775,808.
Allowed range of INT datatype is -2,147,483,648 to 2,147,483,647
You can get more details on the allowed range here
You are trying to insert beyond that range. For phone number better choose VARCHAR data type. If you still want to go for number, in that case use BIGINT instead of INT.

Unable to perform arithmetic operation in select statement in specific scenario

While I was doing some multiplication in select statements, I found a special case where SQL Server is throwing an arithmetic overflow error.
When I executed the same in W3Schools SQL window, it worked. Below is my query where it is throwing error. I tried multiple permutations and combinations but it failed in most of the cases
SELECT 20000000 * 130
Msg 8115, Level 16, State 2, Line 4
Arithmetic overflow error converting expression to data type int.
Message window:
Output window:
Version information:
type int isn't enought, try float.
SELECT cast(20000000 as float) * cast(130 as float)
The result is the data type of the argument with the higher precedence. But if you have 2 int you can't get a float without casting
https://learn.microsoft.com/it-it/sql/t-sql/language-elements/multiply-transact-sql?view=sql-server-2017
I had used float but bigint is the same
SELECT CAST(20000000 AS BIGINT) * 130

Convert INT to BIT

I tried below query
DECLARE #Input INT = 300
DECLARE #Ouput TINYINT
SET #Ouput = #Input
SELECT #Ouput
While execute the above statement, I received the following error.
Arithmetic overflow error for data type tinyint, value = 300.
The input value exceeds the limit, so the error is displayed.
I tried another query
DECLARE #Input INT = 300
DECLARE #Ouput BIT
SET #Ouput = #Input
SELECT #Ouput
When I execute the statement, I really wondered, it doesn't show any error. If the input value <> 0 (negative or positive), the output value always 1.
Converting to bit promotes any nonzero value to 1.
Sqlserver have power or say try to convert value implicitly as your input and output type without using cast or convert function, if you not specify any.
When the conversion error comes in default logic or scenerio, it give the specific type cast error.
So your tiny int and int length is differ, 300 is not fit in tiny int. Tiny int allow max at 255 value
Here is implicit & explicit convertion chart
You receive Arithmetic overflow on setting int value to tinyint because the range of value of int is larger than tinyint (you know. "tiny"(in small voice)).
Anf if you use bit which only have a value of O or 1 , basic in Computer fundamentals , and I think the value you get is 1 because it has a value, if none, you'll get 0.

different in Arithmetic overflow error converting expression to data type int

SELECT SUM(
CASE WHEN RESTORE_TIMESTAMP IS NOT NULL THEN DATEDIFF(MI,SHUTDOWN_TIMESTAMP,RESTORE_TIMESTAMP)
ELSE DATEDIFF(MI,SHUTDOWN_TIMESTAMP,GETDATE()) END )
AS downtime
FROM TX_QUAD_STATUS NOLOCK
and i am getting this error.
Arithmetic overflow error converting expression to data type int.
SUM converts its result into an appropriate datatype, based on the input values. So you might be able to solve this by running the sum against a larger type:
SELECT SUM(
CONVERT(
bigint,
DATEDIFF(minute,SHUTDOWN_TIMESTAMP,COALESCE(RESTORE_TIMESTAMP,GETDATE()))
)
)AS downtime
FROM TX_QUAD_STATUS NOLOCK
If bigint doesn't work, you might try decimal(38,0).
(As mentioned in my comment to your Q, I've also replaced the whole CASE expression with a COALESCE)
Most likely, then, the sum over those rows is huge. You could try casting as bigint, but I suspect you'll get enough idea if you drop the sum, and just look at the individual values:
SELECT
CASE WHEN RESTORE_TIMESTAMP IS NOT NULL THEN DATEDIFF(MI,SHUTDOWN_TIMESTAMP,RESTORE_TIMESTAMP)
ELSE DATEDIFF(MI,SHUTDOWN_TIMESTAMP,GETDATE()) END
AS downtime
FROM TX_QUAD_STATUS NOLOCK
which will give you an idea of the numbers involved. Now add them up - if they are over 2 billion: problems.
The result obtained is bigger that the value that can hold an INT variable.

Resources