What is the correct syntax to create an inline scalar function in SQL Server?
Books Online, in the Types of Functions chapter (2005 and up), talks about Inline Scalar Functions as if they exist and as if no BEGIN...END block is required (in contrast with multiline functions):
For an inline scalar function, there is no function body; the scalar
value is the result of a single statement. For a multistatement scalar function, the function body, defined in a BEGIN...END block, contains a series of Transact-SQL statements that return the single value.
I also noticed a row for "IS: inline scalar function" in the list of object types in the spt_values table:
SELECT name
FROM master..spt_values
WHERE type = 'O9T'
AND name LIKE '%function%'
I have tried to create such a function with no success:
CREATE FUNCTION AddOne(#n int) RETURNS int
AS
RETURN #n + 1
The error message is
Msg 102, Level 15, State 31, Procedure AddOne, Line 3 Incorrect syntax
near 'RETURN'.
Am I missing something or is there an error in Books Online?
Well, AFAIK, none exist (not even in the hidden [mssqlsystemresource] database) and there's no syntax to create one. So it appears that this is something that Microsoft must have anticipated in the run-up to SQL Server 2005 by adding a type for it (and doc!), but never actually implemented for some reason.
Though it is one of the single most requested features for all of Ms Sql Server. Primarily because the default UDF's are so slow and we end up having to back-end ITVF's to get the same effect. (difficult and clumsy, but it works).
Correct, there is no such thing as a inline scalar function. One can be "simulated" by using an inline-TVF, however, the syntax of any "clients" will need to change.
1) create he function:
create function dbo.AddOne(#input int) returns table as return (select #input + 1 as value);
2) in the "client" code, do this...
(select value from dbo.AddOne(Column)) as ColumnPlusOne
An now you have a functioning inline scalar function.
I've had to do this to replace a lot of scalar UDFs in my client code that looked like this...
create function dbo.GetLookupID(#code varchar(50)) returns int
as
begin
declare #return int;
select #return = LookupID from dbo.Lookups where Code = #code;
return #return;
end;
I tried fixing it by removing the variable...
create function dbo.GetLookupID(#code varchar(50)) returns int
as
begin
return (select LookupID from dbo.Lookups where Code = #code);
end;
That was an improvement, however, there was still an unpleasant performance hit. When I changed to the iTVF and changed the calling convention.... it got much better.
Starting with SQL Server 2019 CTP2.1, there is a new feature called "Scalar UDF inlining" which can automatically inline scalar UDFs when certain preconditions are satisfied.
The official blog post introducing the feature is here: https://blogs.msdn.microsoft.com/sqlserverstorageengine/2018/11/07/introducing-scalar-udf-inlining/
Detailed documentation that describes the feature is here:
https://learn.microsoft.com/en-us/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=azuresqldb-current
I'm seeing the same thing. That sentence seems to be the only reference to "inline scalar functions". This article claims that inline table-valued functions can be fast enough to do the job.
Related
Whenever I try to call a stored procedure in PostgreSQL that goes beyond inserting data, it takes forever to run, and it isn't that the query is complicated or the dataset is huge. The dataset is small. I cannot return a table from a stored procedure and I cannot even return 1 row or 1 data point from a stored procedure. It says it is executing the query for a very long time until I finally stop the query from running. It does not give me a reason. I can't let it run for hours. Any ideas on what might be happening? I have included stored procedures that I have tried to call.
Non-working example #1:
CREATE PROCEDURE max_duration(OUT maxD INTERVAL)
LANGUAGE plpgsql AS $$
DECLARE maxD INTERVAL;
BEGIN
SELECT max(public.bikeshare3.duration)
INTO maxD
FROM public.bikeshare3;
END;
$$ ;
CALL max_duration(NULL);
Non-working example #2:
CREATE PROCEDURE getDataByRideId2(rideId varchar(16))
LANGUAGE SQL
AS $$
SELECT rideable_type FROM bikeshare3
WHERE ride_id = rideId
$$;
CALL getDataByRideId2('x78900');
Working example
The only one that worked when called is an insert procedure:
CREATE OR REPLACE PROCEDURE genre_insert_data(GenreId integer, Name_b character varying)
LANGUAGE SQL
AS $$
INSERT INTO public.bikeshare3 VALUES (GenreId, Name_b)
$$;
CALL genre_insert_data(1, 'testName');
FUNCTION or PROCEDURE?
The term "stored procedure" has been a widespread misnomer for the longest time. That got more confusing since Postgres 11 added CREATE PROCEDURE.
You can create a FUNCTION or a PROCEDURE in Postgres. Typically, you want a FUNCTION. A PROCEDURE mostly only makes sense when you need to COMMIT in the body. See:
How to return a value from a stored procedure (not function)?
Nothing in your question indicates the need for a PROCEDURE. You probably want a FUNCTION.
Question asked
Adrian already pointed out most of what's wrong in his comment.
Your first example could work like this:
CREATE OR REPLACE PROCEDURE max_duration(INOUT _max_d interval = NULL)
LANGUAGE plpgsql AS
$proc$
BEGIN
SELECT max(b.duration) INTO _max_d
FROM public.bikeshare3 b;
END
$proc$;
CALL max_duration();
Most importantly, your OUT parameter is visible inside the procedure body. Declaring another instance as variable hides the parameter. You could access the parameter by qualifying with the function name, max_duration.maxD in your original. But that's a measure of last resort. Rather don't introduce duplicate variable names to begin with.
I also did away with error-prone mixed-case identifiers in my answer. See:
Are PostgreSQL column names case-sensitive?
I made the parameter INOUT max_d interval = NULL. By adding a default value, we don't have to pass a value in the call (that's not used anyway). But it must be INOUT instead of OUT for this.
Also, OUT parameters only work for a PROCEDURE since Postgres 14. The release notes:
Stored procedures can now return data via OUT parameters.
While using an OUT parameter, this advise from the manual applies:
Arguments must be supplied for all procedure parameters that lack
defaults, including OUT parameters. However, arguments matching OUT
parameters are not evaluated, so it's customary to just write NULL
for them. (Writing something else for an OUT parameter might cause
compatibility problems with future PostgreSQL versions.)
Your second example could work like this:
CREATE OR REPLACE PROCEDURE get_data_by_ride_id2(IN _ride_id text
, INOUT _rideable_type text = NULL) -- return type?
LANGUAGE sql AS
$proc$
SELECT b.rideable_type
FROM public.bikeshare3 b
WHERE b.ride_id = _ride_id;
$proc$;
CALL get_data_by_ride_id2('x78900');
If the query returns multiple rows, only the first one is returned and the rest is discarded. Don't go there. This only makes sense while ride_id is UNIQUE. Even then, a FUNCTION still seems more suitable ...
In SQL - I have list of user defined function names in a table. based on the logic i need to call/exec the function.
Please my high level code logic below,
DECLARE #MY_FUNCTION VARCHAR(1000);
DECLARE #MY_INPUT_PARAMETER INT;
DECLARE #MY_OUTPUT_PARAMETER INT;
SET #MY_FUNCTION = '' -- Dynamically function name will be provided based on some big logic
--Note: function has input and output parameter
--my query
-- call the function by #MY_FUNCTION (#MY_INPUT_PARAMETER )
#MY_OUTPUT_PARAMETER = EXEC #MY_FUNCTION (#MY_INPUT_PARAMETER)
--Some big sql script using #MY_OUTPUT_PARAMETER
(
-- Script goes here
)
You will need to construct the function with parameters inside the variable and then run sp_execute. Check out the samples in https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql?view=sql-server-ver15#c-using-the-output-parameter
Important
However, try to avoid this method of execution if possible. Let the application decide what SP to call and the SP can then use the right function to make the call. There are two advantages to this.
Your SP will be compiled and SQL will be able to have an execution plan and continue to fine tune it. Hence, better performance
You will have less chances of SQL injections depending on how the table with functions are populated.
I have an UDF like this
CREATE FUNCTION Lob_function
(
#policy NVARCHAR(MAX)
#table Table
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
select #policy=
case
when #policy like '%AMM%' then 'AMM'
when #policy like '%MOT%' then 'MOT'
when #policy like '%MOX%' then 'MOX'
when #policy not like '00%' then LEFT(#policy,3)
end
from #table
return #policy
END;
I want to use my UDF for various cases like :
Select Lob_function (#policy, #table) from #table.
It appears an error on #table Table, when I replace #table by a fixed table, my UDF can be executed but very slowly compared with a normal Select statement.
You can't really do what you want.
First, you cannot pass tables as parameters into functions. As the documentation explains:
Parameters can take the place only of constants; they cannot be used instead of table names, column names, or the names of other database objects.
In theory, you could use dynamic SQL to construct a query and run it. Unfortunately, the only use of execute is for extended functions. This is a little buried in the documentation, but it is there:
User-defined functions cannot make use of dynamic SQL or temp tables. Table variables are allowed.
That really doesn't leave any good options. You might try to think of another approach that doesn't require storing related data in multiple different tables. Or perhaps you can use a stored procedure instead of a UDF.
There are some very arcane work-arounds, which involve using an extended stored procedure or CLR to execute a batch command which in turn passes a query into the database. That is really complicated, prone to error, and I've never actually seen it used in production code.
here is parts of my stored procedure that can not find the function:
(dbo.fn_Get_Order_Contacts_Info_Full_Name(#order_detail_ID, 'Borrower')) As 'Borrower_Contact_Info_Full',
replace(dbo.fn_get_business_product_element_requirements(t_order_detail.order_detail_id,288)
the functions exist in a scalar function as following:
ALTER FUNCTION [dbo].[fn_Get_Order_Contacts_Info_Full_Name]
(
ALTER FUNCTION [dbo].[fn_get_business_product_element_requirements]
(
is there a reason why the proceedure can not find the scalar functions.
my error:
can not find column "dbo" or the user defined function or aggregate " the 2 functions above", or the name is ambiguous.
It's a bit harder to tell without seeing the whole query but here are things to look at:
Are the functions in the database(s) you are refering to in the query?
Did you accidentally create them in two databases, both of which are
referenced in the query?
Are you positive you have typed the names correctly and that they are
in dbo?
Are you sure those are scalar functions?
Have you refreshed the database?
So the way I fixed this issue was with a closure of SQL and restart of the server and it worked. Seems to be a bug in sql server.
Which approach is better to use if I need a member (sp or func) returning 2 parameters:
CREATE PROCEDURE Test
#in INT,
#outID INT OUT,
#amount DECIMAL OUT
AS
BEGIN
...
END
or
CREATE FUNCTION Test
(
#in INT
)
RETURNS #ret TABLE (outID INT, amount DECIMAL)
AS
BEGIN
...
END
What are pros and cons of each approach considering that the result will passed to another stored procedure:
EXEC Foobar #outID, #outAmount
A table valued function can only be used within a scope of a single SELECT statement. It cannot perform DML, catch exceptions etc.
On the other hand, it can return a set which can immediately be joined with another recordset in the same query.
If you use DML or don't need to use the output parameters in the set-based statements, use a stored proc; otherwise create a TVF.
A stored procedure that calls a function :-) I think either will suite you... if your app uses stored procedures for querying the database, then it may be best to be consistent... if you use an ORM, it may not recognize the function... I don't think you can go wrong with either.
In one of my apps, we preferred using the function approach, to throw in another perspective.
HTH.
With the stored procedure using output parameters you will only be able to return the two values: #outID and #amount.
With the table-valued function, you will be able to return a whole set of (outID, amount) tuples. In addition, a table-valued function can be used wherever table or view expressions are allowed in queries, such as:
SELECT dbo.Test(1) AS TestValues
I would argue The output parameter approach is most desirable. This makes it more self documenting that not more than one tuple is expected and I would assume is likely to be more efficient.
I would only use a table-valued function if I needed to obtain a table of values.
If there is only one "row" in your output then it would be preferable to use output parameters in a Stored Procedure.
One exception to this is if your SP/UDF can be written as a single SELECT statement - i.e. an Inline Function - because SQL Server can make better optimizations if you ever need to do something like join it to the output of another query. You may not be doing that now, but writing an inline UDF means you won't be caught off-guard with slow-as-molasses queries and timeout reports if somebody starts using it that way in the future.
If none of that applies to you then I would use a Stored Procedure for the reasons outlined; you don't want to create the illusion of set-based semantics when you aren't actually supporting them.
Output parameters.
Multi-statement table value functions are difficult to trace and tune. Stick with the stored procedure which is easier to troubleshoot.
Also, you are limited to what you can do in a udf. Say you need to add logging, or call an extended stored proc later... you can't use a udf for this.
I think your better bet would be the SP because with the TBF (table value function) you'd have to iterate through the table to get your value.
Bear in mind that if you iterate through the table in SQL, then you'll need to use a CURSOR (which aren't too bad, but can be a little tricky to use).