I have a very simple function to calculate and format a floating point number:
ALTER FUNCTION GetUrbanHours
(
-- Add the parameters for the function here
#driverID int
)
RETURNS int
AS
BEGIN
DECLARE #Result float = 0
SELECT #Result =
FORMAT(
SUM(CONVERT(float,
CONVERT(bigint, RouteSummary.UrbanNightSeconds + RouteSummary.UrbanDaySeconds))/(60*60)),
'##,##0.00')
FROM RouteSummary WHERE DriverID = #driverID
-- Return the result of the function
RETURN #Result
END
When I call the function with a given parameters, I get the following errors:
Msg 8115, Level 16, State 2, Line 6
Arithmetic overflow error converting expression to data type int.
Msg 8114, Level 16, State 5, Line 9
Error converting data type varchar to float.
When I extract the SELECT statement and print #Result, I get the expected result. What is my problem here?
EDIT
I have rewritten the function as follows:
ALTER FUNCTION GetUrbanHours
(
#driverID int
)
RETURNS nvarchar(50)
AS
BEGIN
DECLARE #Result nvarchar(50)
DECLARE #SumSeconds bigint
DECLARE #SumHours float
SELECT #SumSeconds =
SUM(RouteSummary.UrbanNightSeconds + RouteSummary.UrbanDaySeconds)
FROM RouteSummary WHERE DriverID = #driverID
IF #SumSeconds != NULL
BEGIN
SET #SumHours = CONVERT(float, #SumSeconds) / (60*60)
SET #Result =
FORMAT(#SumHours, '##,##0.00')
END
RETURN #Result
END
When I give a parameter that returns rows from the RouteSummary Table, I get NULL. When I give a parameter that returns no rows, I get "Msg 8115, Level 16, state 2, Line 1 Arithmetic overflow error converting expression to data type int." What is wrong now? Also is there a way to identify the line in the function the error message refers to? And why can't I step into the function with the SQL Studio debugger?
To solve the first error do not assign a value to the variable. The second error is caused by your use of the TSQL FORMAT statement. The format statement returns a VARCHAR not a FLOAT. See this link for information on the FORMAT command.
MSDN link
Related
This question already has an answer here:
pgSQL show an error in its function before the SELECT query
(1 answer)
Closed 1 year ago.
When I work with Microsoft SQL Server databases, I sometimes return multiple result sets from stored procedures. I often return so many that it becomes hard to follow which is which. To solve this problem, I follow a convention I learned from a colleague: The 1st result set is a "map", which defines names for the 2nd and other result sets. It has a single record, where every field name is the name of a result set and the corresponding field value is its index in the returned array of result sets. Client code accesses specific result sets by finding out the index by name first.
The following simple example shows the idea:
create or alter procedure divide
#a int,
#b int
as
begin
declare
#error int = 0
-- Name-to-index map
select
1 as result,
2 as error
-- Result
if #b = 0
begin
set #error = 1
select
null as result
end
else
begin
select
#a / #b as result
end
-- Error
select
#error as error
end
In this example, the first result set (index: 0) gives that there are 2 more result sets: one called "result" (index: 1) and another called "error" (index: 2). Both contain only one record: the result of the division and the error code, respectively.
Example call #1:
exec divide #a = 91, #b = 13
Result sets in JSON format:
[
[{ result: 1, error: 2 }],
[{ result: 7 }],
[{ error: 0 }]
]
Example call #2:
exec divide #a = 91, #b = 0
Result sets in JSON format:
[
[{ result: 1, error: 2 }],
[{ result: null }],
[{ error: 1 }]
]
I tried to port this technique to PostgreSQL 14 using the official documentation and especially this page. I came up with this:
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language sql as
$$
declare
ref1 refcursor;
ref2 refcursor;
ref3 refcursor;
error int := 0;
begin
-- Name-to-index map
open ref1 for select
1 as result,
2 as error;
return next ref1;
-- Result
if b = 0 then
error := 1;
open ref2 for select
null as result;
else
open ref2 for select
a / b as result;
end if;
return next ref2;
-- Error
open ref3 for select
error;
return next ref3;
end;
$$;
Unfortunately, I get an error: syntax error at or near "refcursor", referring to the refcursor in the 1st line after declare.
You used the wrong language declaration. Your procedure is in plpgsql but you declared it as plain sql through language sql statement at the top.
Replacing
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language sql as
with
create or replace function divide(
a integer,
b integer
)
returns setof refcursor
language plpgsql as
Solves the problem.
I'm trying to call a table valued function with some variable inputs. However, I'd like to use the function's default value if the calling script doesn't set the input value.
So, assume I have the function (yes, this is a silly function)
create function dbo.testing(#countTo int=10)
returns #output table(num int)
as
begin
declare #i int
set #i=0
while #i < #countTo
begin
set #i=#i+1
insert into #output(num)
values(#i)
end
return
end
Then, I could setup a variable for the input:
declare #stop int
set #stop=15
select * from dbo.testing(#stop)
However, if #stop is left as null, I'd like to use the default value, but it will just use null (which returns nothing...). Essentially, I want the following to work:
select * from dbo.testing(isnull(#stop,default))
But it just returns the error:
Msg 156, Level 15, State 1, Line 4 Incorrect syntax near the keyword
'default'.
Is there anyway to do this without modifying the function?
After researching this a bit my conclusion (I could be wrong though) is that you can't do this without either modifying the function as described below or include a conditional null check when you call the function as:
if #stop is null
select * from dbo.testing(default)
else
select * from dbo.testing(#stop)
This might not be practical though. The alternative is to modify your function to include a null check and default value:
create function dbo.testing(#countTo int)
returns #output table(num int)
as
begin
if #countTo is null set #countto = 10
declare #i int
set #i=0
while #i < #countTo
begin
set #i=#i+1
insert into #output(num)
values(#i)
end
return
end
And call it like select * from dbo.testing(isnull(#stop,null))
In SQL Server 2008 (TSQL), I've created a stored procedure like this:
CREATE PROCEDURE SP_1_10_2
AS
declare #mostValuableBook nvarchar(255)
SELECT #mostValuableBook = Name
FROM books
WHERE price =
( SELECT MAX(price)
FROM books
WHERE izd LIKE '%BHV%' );
return #mostValuableBook
GO
But, when I'm trying to execute it:
declare #x nvarchar(255)
EXECUTE #x = SP_1_10_2;
SELECT 'The most expensive BHV book:', #x AS 'Name'
GO
I'm getting an error:
Conversion failed when converting the nvarchar value 'Internet
Explorer 3 original' to data type int.
It seems like the problem is in the line
EXECUTE #x = SP_1_10_2;
Can you please tell me what's wrong? Why is it trying to convert to int?
RETURN cannot be used to return nvarchar / varchar such as you have. RETURN is used to return an integer, this can be expressed as some sort of status code 1=True / 0=False. Read more about return here: http://msdn.microsoft.com/en-us/library/ms174998.aspx
In your case, you simply need to use OUTPUT variables which is similiar to pass-by-ref in C# or C++. You pass the variable to the sproc, the sproc modifies it, and you get the expected results after a SELECT....
Change it so that your parameters becomes an output parameter:
CREATE PROCEDURE SP_1_10_2
#mostValueableBook nvarchar(255) output
AS
SELECT #mostValuableBook = Name
FROM books
WHERE price =
( SELECT MAX(price)
FROM books
WHERE izd LIKE '%BHV%' );
SELECT #mostValuableBook
GO
Call it like so:
DECLARE #theValBook nvarchar(255)
EXECUTE SP_1_10_2 #mostValuableBook = #theValBook output
Then you can say:
SELECT 'Most expensive book is', #theValBook
You can also create a function to return the value you desire instead of relying on numeric return codes. SQL Functions come in quite handy. See example below which returns the last name with the highest client id using the LIKE operator
Use MYDB
GO
CREATE Function fn_LastClientIdByName
(
#nameLike NVARCHAR(10)
)
RETURNS NVARCHAR(100)
AS
BEGIN
DECLARE #result nvarchar(100)
DECLARE #clientName NVARCHAR(100)
SELECT top 1 #clientName = [clientLast] + ' ' + [clientFirst]
FROM [dbo].[duiClientOnly]
WHERE clientLast like #nameLike + '%'
order by clid desc
select #result = #clientName
return #result
END
I am trying check if a value is null if so the select null else cast to numeric, but it throws an error. This is actually part of an insert statement
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power ='null' then NULL else cast(:power as numeric) end from abc
error that I get is
Error: ERROR: invalid input syntax for type numeric: "null"
:power is a variable that can be given any value using java code. If I give a value of null it give an error.
In code I get the following error from the java stack trace
org.postgresql.util.PSQLException: ERROR: cannot cast type bytea to numeric
Error:
SELECT CASE WHEN 'null' = 'null' THEN NULL ELSE cast('null' AS numeric) END
No error:
DO $$
DECLARE
power text := 'null';
BEGIN
PERFORM CASE WHEN power = 'null' THEN NULL ELSE cast(power AS numeric) END;
END;
$$
Explanation:
If you build a query string, the expression cast('null' AS numeric) or simply 'null'::numeric always raises an exception, even in an ELSE block that is never executed, because it is invalid input syntax and the exception is raised during the syntax check (like the error message implies), not during execution.
A CASE statement like you display only makes sense with a parameter or variable not with literals. The second instance of the literal has no connection to the first instance whatsoever after the query string has been assembled.
For dynamic SQL like that, you need to check the value before you build the query string. Or you use a function or prepared statement and pass the value as parameter. That would work, too.
More advice after comment:
In your particular case you could check the value in the app and build a query string like this:
INSERT INTO tbl(name, abc_id, created, power)
SELECT 'xyz'
, abc_id
, now()
, <insert_value_of_power_or_NULL_here> -- automatically converted to numeric
FROM abc
You may be interested in a different approach to INSERT data from a file conditionally.
Use COPY for files local to the server or psql's meta-command \copy for files local to the client.
if the field value is null, and you want in this case to map it to some value you can use coalesce(field_name, 'Some value') or coalesce(field_name, 123).
For full documentation see here.
You have to check with the IS operator, and not with the equal when you dealing with NULL :
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power IS null then NULL else cast(:power as numeric) end from abc
INSERT into someTable(name,created,power) SELECT 'xyz',now(),
case :power when 'null' then NULL else :power end::numeric from abc
I was trying to do something similar in order to update/insert some records where a numeric value can be null or not.
You can validate a variable before you send it to the function or inside the function depending the value passed
(For me using a variable is better than use CASE WHEN THEN ELSE END CASE every time you need to validate the value)
So to work with the NULL values using a regular comparison operand in order to find a record to update can be done by turning transform_null_equals to ON
I hope this help someone
CREATE OR REPLACE FUNCTION update_insert_transaction(vcodaccount integer, vcodaccountaux text,
vdescription text, vcodgroup integer)
RETURNS integer AS $$
DECLARE
n integer = 0;
vsql text = 'NULL';
BEGIN
IF vcodaccountaux <> '' THEN
vsql = vcodaccountaux;
END IF;
SET LOCAL transform_null_equals TO ON;
EXECUTE 'UPDATE account_import_conf SET (codaccount, codaccountaux, description, codgroup) =
('||vcodaccount||','||vsql||',trim('||quote_literal(vdescription)||'),'||vcodgroup||')
WHERE codaccount='||vcodaccount||' AND codaccountaux = '||vsql||' RETURNING * ';
GET DIAGNOSTICS n = ROW_COUNT;
IF n = 0 THEN
EXECUTE 'INSERT INTO account_import_conf (codaccount, codaccountaux, description, codgroup)
SELECT '||vcodaccount||','||vsql||' ,trim('||quote_literal(vdescription)||'),'||vcodgroup||';';
END IF;
RETURN n;
END;$$
LANGUAGE plpgsql;
Pointedly what I'm asking below is: What is the actual data type of the #cleartext parameter of this SQL function? >> ENCRYPTBYKEY (..) -
http://msdn.microsoft.com/en-us/library/ms174361.aspx
(If you read below this line you can follow the history and reasoning. I think it's trickier than it first appears.)
The SQL Server documentation states the #cleartext (2nd) parameter of the EncryptByKey(..) function can accept a number of various types:
EncryptByKey (#key_GUID , #cleartext [, #add_authenticator, #authenticator] )
#cleartext Is a variable of type
nvarchar, char, varchar, binary,
varbinary, or nchar that contains data
that is to be encrypted with the key.
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ - - - - - But what is its actual declared data type? ...
If I were to create a custom function (totally separate from the EncryptByKey example given above) what actual data type do I give a custom parameter so it will accept all those same types in the same manner?
Edit 1: I'm actually wrapping the SQL EncryptByKey function in a custom
UDF function and I want to recreate the same
parameter types to pass through to
it. This is the reasoning behind my want to create exact same params by type.
Edit 2: If I try using sql_variant it results in the error
Msg 8116, Level 16, State 1, Procedure
EncryptWrapper, Line 17 Argument data
type sql_variant is invalid for
argument 2 of EncryptByKey function.
Edit 3:
Here's my custom wrapper function - and the direct problem. What should the data type of #cleartext be for direct pass through to EncryptByKey?
ALTER FUNCTION [dbo].[EncryptWrapper]
(
#key_GUID uniqueidentifier,
#cleartext -- ??????????? <<< WHAT TYPE ????????????????
#add_authenticator int = 0,
#authenticator sysname = NULL
)
RETURNS varbinary(8000)
AS
BEGIN
-- //Calling a SQL Server builtin function.
-- //Second param #cleartext is the problem. What data type should it be?
Return EncryptByKey(#key_GUID, #cleartext, #add_authenticator, #authenticator)
END
Note: I shouldn't have to use CAST or CONVERT - I only need to use the proper data type for my #cleartext param.
Edit 4: Discovered the EncryptByKey(..) #cleartext parameter is not the following types:
sql_variant- raises error when passed
varbinary- too restrictive- doesn't allow passing of the text types otherwise accepted by EncryptByKey(..)
sysname, nvarchar, varchar- weird behaviour- tends to take only the first character of the argument text or something
try sql_variant:
CREATE FUNCTION [dbo].[yourFunction]
(
#InputStr sql_variant --can not be varchar(max) or nvarchar(max)
)
returns
varchar(8000)
BEGIN
--can use SQL_VARIANT_PROPERTY(#InputStr,'BaseType') to determine given datatype
--do whatever you want with #inputStr here
RETURN CONVERT(varchar(8000),#InputStr) --key is to convert the sql_varient to something you can use
END
GO
the key is to convert the sql_varient to something you can use within the function. you can use IF statements and check the BaseType and convert the sql_varient back into the native data type
EDIT
here is an example of how to get the original datatype:
CREATE FUNCTION [dbo].[yourFunction]
(
#InputStr sql_variant --can not be varchar(max) or nvarchar(max)
)
returns
varchar(8000)
BEGIN
DECLARE #Value varchar(50)
--can use SQL_VARIANT_PROPERTY(#InputStr,'BaseType') to determine given datatype
--do whatever you want with #inputStr here
IF #InputStr IS NULL
BEGIN
SET #value= 'was null'
END
ELSE IF SQL_VARIANT_PROPERTY(#InputStr,'BaseType')='char'
BEGIN
--your special code here
SET #value= 'char('+CONVERT(varchar(10),SQL_VARIANT_PROPERTY(#InputStr,'MaxLength '))+') - '+CONVERT(varchar(8000),#InputStr)
END
ELSE IF SQL_VARIANT_PROPERTY(#InputStr,'BaseType')='datetime'
BEGIN
--your special code here
SET #value= 'datetime - '+CONVERT(char(23),#InputStr,121)
END
ELSE IF SQL_VARIANT_PROPERTY(#InputStr,'BaseType')='nvarchar'
BEGIN
--your special code here
SET #value= 'nvarchar('+CONVERT(varchar(10),CONVERT(int,SQL_VARIANT_PROPERTY(#InputStr,'MaxLength '))/2)+') - '+CONVERT(varchar(8000),#InputStr)
END
ELSE
BEGIN
--your special code here
set #value= 'unknown!'
END
RETURN #value
END
GO
test it out:
DECLARE #x char(5), #z int, #d datetime, #n nvarchar(27)
SELECT #x='abc',#d=GETDATE(),#n='wow!'
select [dbo].[yourFunction](#x)
select [dbo].[yourFunction](#d)
select [dbo].[yourFunction](#z)
select [dbo].[yourFunction](#n)
test output:
-------------------------------------
char(5) - abc
(1 row(s) affected)
-------------------------------------
datetime - 2010-02-17 15:10:44.017
(1 row(s) affected)
-------------------------------------
was null
(1 row(s) affected)
-------------------------------------
nvarchar(27) - wow!
(1 row(s) affected)
ENCRYPTBYKEY() almost certainly isn't written in vanilla T-SQL. It doesn't need to follow T-SQL data typing rules.
That said, if you want to write a wrapper for it, use SQL_VARIANT for the #cleartext parameter, just as KM suggested.
If ENCRYPTBYKEY() is not sensitive to the max length of #cleartext, you could munge all CHAR/VARCHARs to VARCHAR(8000), and all NCHAR/NVARCHARs to NVACHAR(4000).
Otherwise you may be SOL: any data type conversion that respects maximum length--eg, CHAR(10) vs CHAR(20)--will require dynamic SQL, so you would have to write it as a stored procedure, rather than a function. At that point, it's not really a wrapper anymore.