User-Defined Functions:
Like functions in
programming languages, SQL Server user-defined functions are routines that
accept parameters, perform an action, such as a complex calculation, and return
the result of that action as a value. The return value can either be a single
scalar value or a result set.
With SQL Server 2000, Microsoft has introduced the concept of
User-Defined Functions.
Where Can I Use A Function?
Anywhere!
Well, we can use a function almost anywhere that we would use a table
or column. We can use a function anywhere that we can use a scalar value or a
table. Functions can be used in constraints, computed columns,
joins, WHERE clauses, or even in other functions. Functions are an
incredibly powerful part of SQL Server.
Functions can be Deterministic or Nondeterministic:
A deterministic function will return the same result when it is called
with the same set of input parameters. Adding two numbers together is an
example of a deterministic function.
A nondeterministic function, on the other hand, may
return different results every time they are called with the same set of input
values. Even if the state of the data in the database is the same, the results
of the function might be different.
The GETDATE function, for example, is
nondeterministic. One caveat of almost all nondeterministic functions is that
they are executed once per statement, not once per row. If you query 90,000
rows of data and use the RAND function to attempt to produce a random
value for each row you will be disappointed; SQL Server will only generate a
single random number for the entire statement. The only exception to this rule
is NEWID, which will generate a new GUID for every row in the
statement.
When we create a function, SQL Server will analyze
the code we’ve created and evaluate whether the function is deterministic. If
our function makes calls to any nondeterministic functions, it will, itself, be
marked as nondeterministic. SQL Server relies on the author of a SQL CLR
function to declare the function as deterministic using an attribute.
Deterministic functions can be used in indexed views
and computed columns whereas nondeterministic functions cannot.
User-defined functions
Why
use them?
·
They allow modular programming.
You can create the function once, store it in the
database, and call it any number of times in your program. User-defined
functions can be modified independently of the program source code.
·
They allow faster execution.
Similar to stored procedures, Transact-SQL
user-defined functions reduce the compilation cost of Transact-SQL code by
caching the plans and reusing them for repeated executions. This means the
user-defined function does not need to be reparsed and re optimized with each
use resulting in much faster execution times.
CLR functions offer significant performance
advantage over Transact-SQL functions for computational tasks, string
manipulation, and business logic. Transact-SQL functions are better suited for
data-access intensive logic.
·
They can reduce network traffic.
An operation that filters data based on some complex
constraint that cannot be expressed in a single scalar expression can be
expressed as a function. The function can then invoked in the WHERE clause to
reduce the number or rows sent to the client.
NOTE: Transact-SQL
user-defined functions in queries can only be executed on a single thread
(serial execution plan).
Components of a User-defined
Function:
User-defined functions can be written in Transact-SQL, or in
any .NET programming language. For more information about using .NET languages
in functions, see CLR
User-Defined Functions.
All user-defined functions have the same two-part structure:
a header and a body. The function takes zero or more input parameters and
returns either a scalar value or a table.
The header
defines:
Function
name with optional schema/owner name
Input
parameter name and data type
Options
applicable to the input parameter
Return parameter
data type and optional name
Options
applicable to the return parameter
The body defines
the action, or logic, the function is to perform. It contains either:
One or
more Transact-SQL statements that perform the function logic
A
reference to a .NET assembly
IF OBJECT_ID (N'dbo.GetWeekDay', N'FN') IS NOT NULL
DROP FUNCTION dbo.GetWeekDay;
GO
CREATE FUNCTION dbo.GetWeekDay -- function name
(@Date datetime) -- input parameter name and data type
RETURNS int -- return parameter data type
AS
BEGIN -- begin body definition
RETURN DATEPART (weekday, @Date) -- action performed
END;
GO
The following example shows the function used in a
Transact-SQL statement.
SELECT dbo.GetWeekDay(CONVERT(DATETIME,'20020201',101)) AS DayOfWeek;
GO
Types of functions:
Scalar
Function:
User-defined scalar functions return a single data value of the type defined in the RETURNS clause. For an
inline scalar function, there is no function body; the scalar value is the
result of a single statement. For a multi statement scalar function, the
function body, defined in a BEGIN...END block, contains a series of
Transact-SQL statements that return the single value.
The return type can be any data type except text, ntext, image, cursor, and timestamp.
They are deprecated and will be removed in a future version of SQL Server.
The following example creates a multi statement scalar
function in the AdventureWorks2012 database. The function takes one input
value, a ProductID, and returns a single data value, the aggregated
quantity of the specified product in inventory.
IF
OBJECT_ID (N'dbo.ufnGetInventoryStock', N'FN') IS NOT NULL
DROP FUNCTION ufnGetInventoryStock;
GO
CREATE
FUNCTION dbo.ufnGetInventoryStock(@ProductID int)
RETURNS
int
AS
-- Returns
the stock level for the product.
BEGIN
DECLARE @ret int;
SELECT @ret = SUM(p.Quantity)
FROM Production.ProductInventory p
WHERE p.ProductID = @ProductID
AND p.LocationID = '6';
IF (@ret IS NULL)
SET @ret = 0;
RETURN @ret;
END;
GO
SELECT
ProductModelID, Name, dbo.ufnGetInventoryStock(ProductID)AS CurrentSupply
FROM
Production.Product
WHERE ProductModelID
BETWEEN 75 and 80;
Table-Valued
Functions:
User-defined table-valued functions return a table data type.
A
table-valued user-defined function can also replace stored procedures that
return a single result set. The table returned by a user-defined function can
be referenced in the FROM clause of a Transact-SQL statement, but stored
procedures that return result sets cannot.
Inline
User-defined Function Rules
Inline
user-defined functions follow these rules:
·
The RETURNS clause contains only the
keyword table. You do not have to define the format of a return variable,
because it is set by the format of the result set of the SELECT statement in
the RETURN clause.
·
There is no function body delimited
by BEGIN and END.
·
The RETURN clause contains a single SELECT
statement in parentheses. The result set of the SELECT statement forms the
table returned by the function. The SELECT statement used in an inline function
is subject to the same restrictions as
SELECT statements used in views.
·
The table-valued function accepts only
constants or @local_variable arguments
For an inline table-valued function, there is no function
body; the table is the result set of a single SELECT statement.
Inline User-Defined Functions:
Inline
user-defined functions are a subset of user-defined functions that return
a table data type. Inline functions can be used to achieve the
functionality of parameterized views.
The
following example returns store names and cities for a specified region.
USE AdventureWorks2008R2;
GO
CREATE VIEW CustomersByRegion
AS
SELECT DISTINCT S.Name AS Store, A.City
FROM Sales.Store AS S
JOIN Sales.BusinessEntityAddress AS BEA ON BEA.BusinessEntityID = S.BusinessEntityID
JOIN Person.Address AS A ON A.AddressID = BEA.AddressID
JOIN Person.StateProvince SP ON
SP.StateProvinceID = A.StateProvinceID
WHERE SP.Name = N'Washington';
GO
This view would be better if it were more generalized and let
users specify the region they are interested in viewing. Views, however, do not
support parameters in the search conditions specified in the WHERE clause.
Inline user-defined functions can be used to support parameters in the search
conditions specified in the WHERE clause. The following example creates an
inline function that allows users to specify the region in their query:
USE AdventureWorks2008R2;
GO
IF OBJECT_ID(N'Sales.ufn_CustomerNamesInRegion', N'IF') IS NOT NULL
DROP FUNCTION Sales.ufn_CustomerNamesInRegion;
GO
CREATE FUNCTION Sales.ufn_CustomerNamesInRegion
( @Region nvarchar(50) )
RETURNS table
AS
RETURN (
SELECT DISTINCT s.Name AS Store, a.City
FROM Sales.Store AS s
INNER JOIN Person.BusinessEntityAddress AS bea
ON bea.BusinessEntityID = s.BusinessEntityID
INNER JOIN Person.Address AS a
ON a.AddressID = bea.AddressID
INNER JOIN Person.StateProvince AS sp
ON sp.StateProvinceID = a.StateProvinceID
WHERE sp.Name = @Region
);
GO
-- Example of calling the function for a specific region
SELECT *
FROM Sales.ufn_CustomerNamesInRegion(N'Washington')
ORDER BY City;
GO
Inline Functions and Indexed Views:
Inline functions can also be used to increase the power of
indexed views. The indexed view itself cannot use parameters in its WHERE
clause search conditions to tailor the stored result set to specific users. You
can, however, define an indexed view that stores the complete set of data that
matches the view, and then define an inline function over the indexed view that
contains parameterized search conditions that allow users to tailor their
results. If the view definition is complex, most of the work performed to build
a result set involves operations such as building aggregates or joining several
tables when the clustered index is created on the view.
If you then create an inline function that references the
indexed view, the function can apply the user's parameterized filters to return
specific rows from the materialized result set of the indexed view. For
example:
You define a view vw_QuarterlySales that aggregates
all sales data into a result set that reports summarized sales data by quarter
for all stores.
You create a clustered index on vw_QuarterlySales to
materialize a result set containing the summarized data.
You create an inline function to filter the summarized data:
CREATE FUNCTION dbo.ufn_QuarterlySalesByStore
(
@StoreID int )
RETURNS table
AS
RETURN (
SELECT *
FROM
SalesDB.dbo.vw_QuarterlySales
WHERE
StoreID = @StoreID
)
Users can then get the data for their specific store by
selecting from the inline function:
SELECT *
FROM
fn_QuarterlySalesByStore(14432)
Most of the work needed to satisfy the queries issued at Step
4 is to aggregate the sales data by quarter. This work is done once at Step 2.
Each individual SELECT statement in Step 4 uses the function fn_QuarterlySalesByStore to
filter out the aggregated data specific to their store.
Components of a
Table-Valued User-defined Function
In
a table-valued user-defined function:
·
The RETURNS clause defines a local return
variable name for the table returned by the function. The RETURNS clause also
defines the format of the table. The scope of the local return variable name is
local within the function.
·
The Transact-SQL statements in the function
body build and insert rows into the return variable defined by the RETURNS
clause.
·
When a RETURN statement is executed, the rows
inserted into the variable are returned as the tabular output of the function.
The RETURN statement cannot have an argument.
USE
AdventureWorks;
GO
IF OBJECT_ID(N'dbo.ufnGetContactInformation', N'TF')
IS NOT NULL
DROP FUNCTION dbo.ufnGetContactInformation;
GO
CREATE FUNCTION dbo.ufnGetContactInformation(@ContactID int)
RETURNS
@retContactInformation TABLE
(
--
Columns returned by the function
ContactID int PRIMARY KEY NOT NULL,
FirstName nvarchar(50) NULL,
LastName nvarchar(50) NULL,
JobTitle nvarchar(50) NULL,
ContactType nvarchar(50) NULL
)
AS
-- Returns the
first name, last name, job title, and contact type for the specified contact.
BEGIN
DECLARE
@FirstName nvarchar(50),
@LastName nvarchar(50),
@JobTitle nvarchar(50),
@ContactType nvarchar(50);
..................
..................
..................
..................
..................
..................
-- Return the
information to the caller
IF
@ContactID IS NOT
NULL
BEGIN
INSERT
@retContactInformation
SELECT
@ContactID, @FirstName,
@LastName, @JobTitle,
@ContactType;
END;
RETURN;
END;
GO
No
Transact-SQL statements in a table-valued function can return a result set
directly to a user. The only information the function can return to the user is
the table returned by the function.
The
body of a TVF can either contain just a single statement or multiple
statements, but the two cases are handled very differently by the optimizer. If
the function body contains just a single statement (often referred to as an
“inline TVF”), then the optimizer treats it in a similar fashion to a view in
that it will “decompose” it and simply reference the underlying objects (there
will be no reference to the function in the resulting execution plan).
However,
by contrast, multi-statement TVFs present an optimization problem for SQL
Server; it doesn’t know what to do with them. It treats them rather like a
table for which it has no available statistics – the optimizer assumes that it
the TVF will always return one row. As a result, even a very simple
multi-statement TVF can cause severe performance problems.
The thing to remember is, there are no statistics generated
for these things. That means the optimizer thinks they return a single row of
data. When they do only return a few rows, everything is fine. When they return
even as little as a hundred rows, like the example I’m posting below, they
stink.
The
following example uses the table-valued function dbo.ufnGetContactInformation in
the FROM clause of two SELECT statements.
USE
AdventureWorks;
GO
SELECT ContactID, FirstName, LastName, JobTitle,
ContactType
FROM dbo.ufnGetContactInformation(2200);
GO
SELECT ContactID, FirstName, LastName, JobTitle,
ContactType
FROM dbo.ufnGetContactInformation(5);
GO
Built-in
Functions:
Built-in
functions are provided by SQL Server to help you perform a variety of
operations. They cannot be modified. You can use built-in functions in
Transact-SQL statements to:
·
Access information from SQL Server system
tables without accessing the system tables directly. For more information,
see Using System Functions.
·
Perform common tasks such as SUM, GETDATE, or
IDENTITY. For more information, see Built-in Functions (Transact-SQL).
Built-in
functions return either scalar or table data types. For example,
@@ERROR returns 0 if the last Transact-SQL statement executed successfully. If
the statement generated an error, @@ERROR returns the error number. And the function
SUM(parameter) returns the sum of all the values for the parameter.
Creating
User-Defined Functions (Database Engine)
User-defined functions are created using the CREATE
FUNCTION statement, modified using the ALTER FUNCTION statement, and removed
using the DROP FUNCTION statement. Each fully qualified user-defined function
name (schema_name.function_name) must be unique.
Guidelines:
Transact-SQL
errors that cause a statement to be canceled and continue with the next
statement in the module (such as triggers or stored procedures) are treated differently
inside a function. In functions, such errors cause the execution of the
function to stop. This in turn causes the statement that invoked the function
to be canceled.
The
statements in a BEGIN...END block cannot have any side effects. Function side
effects are any permanent changes to the state of a resource that has a scope
outside the function such as a modification to a database table. The only
changes that can be made by the statements in the function are changes to
objects local to the function, such as local
cursors or variables. Modifications to database tables, operations on
cursors that are not local to the function, sending e-mail, attempting a
catalog modification, and generating a result set that is returned to the user
are examples of actions that cannot be performed in a function.
NOTE: If a CREATE FUNCTION statement produces side effects
against resources that do not exist when the CREATE FUNCTION statement is
issued, SQL Server executes the statement. However, SQL Server does not execute
the function when it is invoked.
The
number of times that a function specified in a query is actually executed can
vary between execution plans built by the optimizer. An example is a function
invoked by a subquery in a WHERE clause. The number of times the subquery and
its function is executed can vary with different access paths chosen by the
optimizer.
Valid statements in a
function:
The
types of statements that are valid in a function include:
·
DECLARE statements can be used to define data variables
and cursors that are local to the function.
·
Assignments of values to objects local to the
function, such as using SET to assign values to scalar and table local
variables.
·
Cursor operations that reference local cursors that
are declared, opened, closed, and deallocated in the function. FETCH statements
that return data to the client are not allowed. Only FETCH statements that
assign values to local variables using the INTO clause are allowed.
·
Control-of-flow statements except TRY...CATCH
statements.
·
SELECT statements containing select lists with
expressions that assign values to variables that are local to the function.
·
UPDATE, INSERT, and DELETE statements modifying
table variables that are local to the function.
·
EXECUTE statements calling an extended stored
procedure.
Built-in system functions
The
following nondeterministic built-in functions can be used in Transact-SQL
user-defined functions.
CURRENT_TIMESTAMP
|
@@MAX_CONNECTIONS
|
GET_TRANSMISSION_STATUS
|
@@PACK_RECEIVED
|
GETDATE
|
@@PACK_SENT
|
GETUTCDATE
|
@@PACKET_ERRORS
|
@@CONNECTIONS
|
@@TIMETICKS
|
@@CPU_BUSY
|
@@TOTAL_ERRORS
|
@@DBTS
|
@@TOTAL_READ
|
@@IDLE
|
@@TOTAL_WRITE
|
@@IO_BUSY
|
The
following nondeterministic built-in functions cannot be used in Transact-SQL user-defined
functions.
NEWID
|
RAND
|
NEWSEQUENTIALID
|
TEXTPTR
|
For
a list of deterministic and nondeterministic built-in system functions, see Deterministic
and Nondeterministic Functions.
Schema-bound functions
CREATE
FUNCTION supports a SCHEMABINDING clause that binds the function to the schema
of any objects it references, such as tables, views, and other user-defined
functions. An attempt to alter or drop any object referenced by a schema-bound
function fails.
·
All views and user-defined functions referenced by
the function must be schema-bound.
·
All objects referenced by the function must be in
the same database as the function. The objects must be referenced using either
one-part or two-part names.
·
You must have REFERENCES permission on all objects
(tables, views, and user-defined functions) referenced in the function.
You
can use ALTER FUNCTION to remove the schema binding. The ALTER FUNCTION
statement should redefine the function without specifying WITH SCHEMABINDING.
Specifying parameters:
A
user-defined function takes zero or more input parameters and returns either a
scalar value or a table. A function can have a maximum of 1024 input
parameters. When a parameter of the function has a default value, the keyword
DEFAULT must be specified when calling the function to get the default value.
This behavior is different from parameters with default values in user-defined
stored procedures in which omitting the parameter also implies the default
value. User-defined functions do not support output parameters.
Executing
User-Defined Functions (Database Engine):
User-defined
functions can be invoked in queries or in other statements or expressions such
as computed columns or string expressions. Scalar-valued functions can be
executed using the EXECUTE statement.
Invoking
User-defined Functions That Return a Scalar Value:
You
can invoke a user-defined function that returns a scalar value anywhere that a
scalar expression of the same data type is allowed in Transact-SQL statements.
Scalar-valued functions must be invoked by using at least the two-part name of
the function. For more information about multipart names.
User-defined
functions that return scalar values are allowed in these locations:
As
an expression in the select_list of a SELECT statement:
USE
AdventureWorks;
GO
SELECT
ProductID, ListPrice,
dbo.ufnGetProductDealerPrice(ProductID, StartDate) AS DealerPrice,
StartDate,
EndDate
FROM
Production.ProductListPriceHistory
WHERE
ListPrice > .0000
ORDER BY ProductID,
StartDate;
GO
As an expression or string_expression in
a WHERE or HAVING clause predicate:
USE
AdventureWorks;
GO
SELECT
ProductID, ListPrice,
StartDate, EndDate
FROM
Production.ProductListPriceHistory
WHERE
dbo.ufnGetProductDealerPrice(ProductID, StartDate) > .0000
ORDER BY ProductID,
StartDate;
GO
As a group_by_expression in
a GROUP BY clause.
As an order_by_expression in
an ORDER BY clause.
As an expression in
the SET clause in an UPDATE statement:
USE
AdventureWorks;
GO
UPDATE
Production.ProductListPriceHistory
SET
ListPrice = dbo.ufnGetProductDealerPrice(ProductID, StartDate)
WHERE
ProductID > 900;
GO
As an expression in
the VALUES clause of an INSERT statement:
User-defined functions referenced in these locations are
logically executed one time per row.
CHECK
Constraints
User-defined
functions that return scalar values can be invoked in CHECK constraints if the
argument values passed to the function reference columns only in the table or
constants. Every time the query processor checks the constraint, the query
processor calls the function with the argument values associated with the
current row being checked. The owner of a table must also be the owner of the
user-defined function invoked by a CHECK constraint on the table.
DEFAULT
Definitions
User-defined
functions can be invoked as the constant_expression of DEFAULT
definitions if the argument values passed to the function only contain
constants. The owner of the table must also be the owner of the user-defined
function invoked by a DEFAULT definition on the table.
Computed
Columns
Functions
can be invoked by computed columns if the argument values passed to the
function reference only columns in the table or constants. The owner of the
table must also be the owner of the user-defined function invoked by a computed
column in the table.
Assignment
Operators
Assignment
operators (left_operand = right_operand) can invoke user-defined
functions that return a scalar value in the expression specified as the right
operand.
Control-of-Flow
Statements
User-defined
functions that return scalar values can be invoked by control-of-flow
statements in their Boolean expressions.
CASE
Expressions
User-defined
functions that return a scalar value can be invoked in any of the CASE
expressions.
PRINT
Statements
User-defined
functions that return a character string can be invoked as the string_expr expression
of PRINT statements.
Functions and
Stored Procedures
Function
arguments can also be a reference to a user-defined function that returns a
scalar value.
RETURN integer_expression statements
in stored procedures can invoke user-defined functions that return an integer
as the integer_expression.
RETURN return_type_spec statements
in user-defined functions can invoke user-defined functions that return a
scalar data type such as the return_type_spec, provided the value returned
by the invoked user-defined function can be implicitly converted to the return
data type of the invoking function.
Executing
User-defined Functions That Return a Scalar Value
You can
execute user-defined functions that return scalar values in the same manner as
stored procedures. When executing a user-defined function that returns a scalar
value, the parameters are specified in the same way they are for stored
procedures:
The argument values are not enclosed in
parentheses.
Parameter names can be specified.
If parameter names are specified, the argument
values do not have to be in the same sequence as the parameters.
The
following example creates a user-defined function that returns a decimal scalar
value.
IF OBJECT_ID(N'dbo.ufn_CubicVolume', N'FN') IS NOT NULL
DROP FUNCTION dbo.ufn_CubicVolume;
GO
CREATE FUNCTION dbo.ufn_CubicVolume
-- Input dimensions in centimeters.
(@CubeLength decimal(4,1), @CubeWidth decimal(4,1),
@CubeHeight decimal(4,1) )
RETURNS decimal(12,3) -- Cubic Centimeters.
WITH SCHEMABINDING
AS
BEGIN
RETURN ( @CubeLength * @CubeWidth * @CubeHeight )
END;
GO
The
following example executes the dbo.ufn_CubicVolume function. Using the Transact-SQL EXECUTE
statement, the arguments are identified in an order different from the
parameters in the function definition:
DECLARE @MyDecimalVar decimal(12,3);
EXEC @MyDecimalVar = dbo.ufn_CubicVolume @CubeLength = 12.3,
@CubeHeight = 4.5, @CubeWidth = 4.5;
SELECT @MyDecimalVar;
GO
Invoking
User-defined Functions That Return a table Data Type:
You can
invoke a user-defined function that returns a table where table
expressions are allowed in the FROM clause of SELECT, INSERT, UPDATE, or DELETE
statements. An invocation of a user-defined function that returns a table can
be followed by an optional table alias. The following example illustrates
calling the table-valued function dbo.ufnGetContactInformation in the
FROM clause of a SELECT statement.
SQL
USE AdventureWorks;
GO
SELECT ContactID, FirstName, LastName, JobTitle,
ContactType
FROM dbo.ufnGetContactInformation(2200);
GO
SELECT ContactID, FirstName, LastName, JobTitle,
ContactType
FROM dbo.ufnGetContactInformation(5);
GO
When a
user-defined function that returns a table is invoked in the FROM clause of a
subquery, the function arguments cannot reference any columns from the outer
query.
Static,
read-only cursors are the only type of cursor that can be opened on a SELECT
statement whose FROM clause refers to a user-defined function that returns a
table.
A
SELECT statement that references a user-defined function that returns a table invokes
the function one time.
Invoking
Built-in Table-valued Functions
There
are several built-in table-valued functions that return a table value. The
invocation of these built-in user-defined functions can be either unqualifed or
can use the sys schema qualifier. You should use the sys schema
qualifier for built-in table-valued functions, because it prevents conflicts
with user-defined functions of the same name. The following example shows how
to invoke the system built-in function fn_helpcollations.
SELECT
*
FROM
sys.fn_helpcollations();
GO
Using Hints
with Table-valued Functions:
When
you create a user-defined function, you can apply a table hint in any queries
that form the function definition. Hints applied to views that reference
Transact-SQL table-valued functions are also applied to the functions. These
functions can conflict with the hints in the function definition. For more
information, see View Resolution.
You
cannot apply hints on views that reference CLR table-valued functions.
Note:The ability of
the Database Engine to apply hints on views to multi-statement table-valued
functions that are part of the view definition will be removed in a future
version of SQL Server.
You
cannot apply a table hint to the result of any table-valued function in the
FROM clause of a query.
Permissions:
Requires
CREATE FUNCTION permission in the database and ALTER permission on the schema
in which the function is being created. If the function specifies a
user-defined type, requires EXECUTE permission on the type.
Limitations and
restrictions:
·
User-defined functions cannot be used to perform
actions that modify the database state.
·
User-defined functions cannot contain an OUTPUT INTO
clause that has a table as its target.
·
User-defined functions cannot return multiple result
sets. Use a stored procedure if you need to return multiple result sets.
·
User-defined functions cannot call a stored
procedure, but can call an extended stored procedure.
·
User-defined functions cannot make use of dynamic
SQL or temp tables. Table variables are allowed.
·
SET statements are not allowed in a user-defined
function.
·
The FOR XML clause is not allowed
·
User-defined functions can be nested; that is, one
user-defined function can call another. The nesting level is incremented when
the called function starts execution, and decremented when the called function
finishes execution. User-defined functions can be nested up to 32 levels.
Exceeding the maximum levels of nesting causes the whole calling function chain
to fail. Any reference to managed code from a Transact-SQL user-defined
function counts as one level against the 32-level nesting limit. Methods
invoked from within managed code do not count against this limit.
·
The following Service Broker statements cannot
be included in
the definition of a Transact-SQL user-defined function:
o BEGIN
DIALOG CONVERSATION
o END
CONVERSATION
o GET
CONVERSATION GROUP
o MOVE
CONVERSATION
o RECEIVE
o SEND
- Statements
with side effects (insert/update/delete) and temporary tables may not be
used. You can, however, use table variables. Table variables are allowed
in UDFs because they are created as variables, not through DDL. DDL is
viewed as producing a side effect and is not allowed.
- TRY/CATCH statements
are not allowed since CATCH can have the side effect of masking
the error state of a given function.
Error handling is restricted in a user-defined
function. A UDF does not support TRY…CATCH, @ERROR or RAISERROR.
Execute User-defined
Functions
Execute a user defined
function using Transact-SQL.
Limitations and
restrictions
In
Transact-SQL, parameters can be supplied either by using value or
by using @parameter_name=value. A
parameter is not part of a transaction; therefore, if a parameter is changed in
a transaction that is later rolled back, the value of the parameter does not
revert to its previous value. The value returned to the caller is always the
value at the time the module returns.
Security
Permissions
are not required to run the EXECUTE statement.
However, permissions are
required on
the securables referenced within the EXECUTE string. For example, if the string
contains an INSERT statement,
the caller of the EXECUTE statement must have INSERT permission on the target
table. Permissions are checked at the time EXECUTE statement is encountered,
even if the EXECUTE statement is included within a module. For more
information, see EXECUTE
(Transact-SQL)
Modify User-defined
Functions
You can modify
user-defined functions in SQL Server 2016 by using SQL Server Management Studio
or Transact-SQL. Modifying user-defined functions as described below will not
change the functions’ permissions, nor will it affect any dependent functions,
stored procedures, or triggers.
-- Scalar-Valued Function
USE
[AdventureWorks2012]
GO
ALTER FUNCTION [dbo].[ufnGetAccountingEndDate]()
RETURNS [datetime]
AS
BEGIN
RETURN
DATEADD(millisecond, -2, CONVERT(datetime, '20040701', 112));
END;
Delete User-defined
Functions
You can delete (drop)
user-defined functions in SQL Server 2016 by using SQL Server Management Studio
or Transact-SQL
Limitations and
Restrictions
·
You will not be able to delete the function if there
are Transact-SQL functions or views in the database that reference this
function and were created by using SCHEMABINDING, or if there are computed
columns, CHECK constraints, or DEFAULT constraints that reference the function.
·
You will not be able to delete the function if there
are computed columns that reference this function and have been indexed.
Security
Permissions
Requires
ALTER permission on the schema to which the function belongs, or CONTROL
permission on the function.
USE AdventureWorks2012;
GO
-- determines if function exists in database
IF OBJECT_ID (N'Sales.fn_SalesByStore', N'IF') IS NOT NULL
-- deletes function
DROP
FUNCTION Sales.fn_SalesByStore;
GO
Rename User-defined
Functions:
You can rename
user-defined functions in SQL Server 2016 by using SQL Server Management Studio
or Transact-SQL.
Limitations and
Restrictions
·
Renaming a user-defined function will not change the
name of the corresponding object name in the definition column of the sys.sql_modules catalog
view. Therefore, we recommend that you do not rename this object type. Instead,
drop and re-create the stored procedure with its new name.
·
Changing the name or definition of a user-defined
function can cause dependent objects to fail when the objects are not updated
to reflect the changes that have been made to the function.
Security
Permissions
To
drop the function, requires either ALTER permission on the schema to which the
function belongs or CONTROL permission on the function. To re-create the
function, requires CREATE FUNCTION permission in the database and ALTER
permission on the schema in which the function is being created.
Using SQL Server
Management Studio
To rename user-defined
functions
1.
In Object
Explorer, click the plus sign next to the database that
contains the function you wish to rename and then
2.
Click the plus sign next to the Programmability folder.
3.
Click the plus sign next to the folder that contains
the function you wish to rename:
o Table-valued
Function
o Scalar-valued
Function
o Aggregate
Function
4.
Right-click the function you wish to rename and
select Rename.
5.
Enter the function’s new name.
Using Transact-SQL
To
rename user-defined functions
This task cannot be performed using Transact-SQL
statements. To rename a user-defined function using
Transact-SQL, you must first delete the existing function and then re-create it
with the new name. Ensure that all code and applications that used the
function’s old name now use the new name.
View User-defined
Functions:
Several
system stored procedures and catalog views provide information about stored
procedures. Using these, you can:
See the definition of the function. That is,
the Transact-SQL statements used to create a user-defined function. This can be
useful if you do not have the Transact-SQL script files used to create the
function.
Get information about a function such as its
schema, when it was created, and its parameters.
List the objects used by the specified
function, and the objects that use the specified function. This information can
be used to identify the functions affected by the changing or removal of an
object in the database.
To view
the definition of a user-defined function
To view
information about a user-defined function
To view
the dependencies of a user-defined function
Security
Permissions
Using sys.sql_expression_dependencies to find all the dependencies on a
function requires VIEW DEFINITION permission on the database and SELECT
permission on sys.sql_expression_dependencies for the database. System object
definitions, like the ones returned in OBJECT_DEFINITION, are publicly visible.
Rewriting Stored Procedures as
Functions
This topic describes how to determine whether to rewrite
existing stored procedure logic as user-defined functions. For example, if you
want to invoke a stored procedure directly from a query, repackage the code as
a user-defined function.
In general, if the stored procedure returns a, single, result
set, define a table-valued function. If the stored procedure computes a scalar
value, define a scalar function.
Criteria for Table-Valued Functions
If a stored procedure meets the following criteria, it is a
good candidate for being rewritten as a table-valued function:
The
logic is expressible in a single SELECT statement but is a stored procedure,
rather than a view, only because of the need for parameters. This scenario can
be handled with an inline table-valued function.
The stored procedure does not perform update
operations, except to table variables.
There is no need for dynamic EXECUTE statements
The stored procedure returns one result set.
The
primary purpose of the stored procedure is to build intermediate results that
are to be loaded into a temporary table, which is then queried in a SELECT
statement. INSERT...EXEC statements can be written using table-valued
functions. For example, consider the following sequence:
INSERT #temp EXEC sp_getresults
SELECT ...
FROM #temp, t1
WHERE ...
The sp_getresults stored
procedure can be rewritten as a table-valued function, for example fn_results(),
which means the preceding statements can be rewritten as:
INSERT #temp
SELECT ...
FROM fn_results(), t1
WHERE ...
Rewriting
Extended Stored Procedures Using CLR
CLR
functions provide a more reliable and scalable alternative to extended stored
procedures. Many extended stored procedures perform some computational task
that is harder to express in Transact-SQL. Such stored procedures can be
rewritten using the CLR given the benefits described above. Further, extended
stored procedures that return result sets by accessing an external resource
such as a file or Web Service can be rewritten using a CLR table-valued
function. For more information, see Creating CLR
Functions.
Performance
Considerations:
Issue1:
IF OBJECT_ID(N'Production.ProductCostDifference', N'FN') IS NOT NULL
DROP FUNCTION
Production.ProductCostDifference ;
GO
CREATE FUNCTION
Production.ProductCostDifference
(
@ProductId INT ,
@StartDate DATETIME ,
@EndDate DATETIME
)
RETURNS MONEY
AS
BEGIN
DECLARE @StartingCost AS
MONEY ;
DECLARE @CostDifference AS
MONEY ;
SELECT TOP 1
@StartingCost = pch.StandardCost
FROM
Production.ProductCostHistory AS pch
WHERE pch.ProductID =
@ProductId
AND EndDate BETWEEN
@StartDate
AND @EndDate
ORDER BY StartDate ASC ;
SELECT TOP 1
@CostDifference = StandardCost - @StartingCost
FROM
Production.ProductCostHistory AS pch
WHERE pch.ProductID =
@ProductId
AND EndDate BETWEEN
@StartDate
AND @EndDate
ORDER BY StartDate DESC ;
RETURN @CostDifference ;
END
SELECT Production.ProductCostDifference(707, '1999-01-01', GETDATE()) ;
/*
column1
----------
1.8504
*/
--QUERY 1
SELECT ProductID ,
Name AS
ProductName ,
Production.ProductCostDifference
(ProductID, '2000-01-01', GETDATE())
AS
CostVariance
FROM Production.Product
;
--QUERY 2
SELECT ProductID ,
Name AS
ProductName ,
Production.ProductCostDifference
(ProductID, '2000-01-01', GETDATE())
AS
CostVariance
FROM Production.Product
WHERE Production.ProductCostDifference
(ProductID, '2000-01-01', GETDATE())
IS NOT NULL ;
Unfortunately,
SQL Server is not terribly intelligent in the way that it works with scalar functions.
In the first query, our ProductCostDifference function will be executed once for each of the 504 rows in
the Production.Product table. This leads to an increase in disk access, CPU
utilization, and memory utilization.
We can
see that all data is read from disk in the Index Scan operator before being
sent to the Compute Scalar operator, where our function is applied to the data.
If we open up the Properties page for the Compute Scalar node (pressing F4 will
do this if you haven’t changed the default SQL Server Management Studio
settings) and examine the Define Values property list. If this references the
function name (rather than the column name), as it will in this case, then the
function is being called once per row.
StmtText
----------------------------------------------------------------------------------- |--ComputeScalar(DEFINE:([Expr1003]=[AdventureWorks2008].[Production].[ProductCostDifference]([AdventureWorks2008].[Production].[Product].[ProductID],'2000-01-01
00:00:00.000',getdate())))
|--IndexScan(OBJECT:([AdventureWorks2008].[Production].[Product].[AK_Product_Name]))
The situation
is even worse for the second query in Listing 4 in that the function needs to be
evaluated twice: once for every 504 rows in the Production.Product table and once again for the 157 rows that produce a non-NULL result from our scalar function. The execution plan for this query is as shown below.
StmtText
----------------------------------------------------------------------------------- |--ComputeScalar(DEFINE:([Expr1003]=[AdventureWorks2008].[Production].[ProductCostDifference]([AdventureWorks2008].[Production].[Product].[ProductID],'2000-01-01
00:00:00.000',getdate())))
|--Filter(WHERE:([AdventureWorks2008].[Production].[ProductCostDifference]([AdventureWorks2008].[Production].[Product].[ProductID],'2000-01-01
00:00:00.000',getdate()) IS NOT NULL))
|--IndexScan(OBJECT:([AdventureWorks2008].[Production].[Product].[AK_Product_Name]))
(3
row(s) affected)
Again,
we can establish whether or not a function is being executed once per row by
examining the details of this plan; in this case, the properties of either the Compute Scalar or the Filter node.
The Predicate property of the Filter node shows that that the filter operation is filtering
on:
:([AdventureWorks2008].[Production].[ProductCostDifference]([AdventureWorks2008].[Production].[Product].[ProductID],'2000-01-01
00:00:00.000',getdate()) IS NOT NULL)
In
other words, SQL Server is evaluating the function once for every row in the
product table. No function ‘inlining’ has been performed; we would be able to
see the ‘inlined’ source code if it had been.
This
may seem like a trivial point to labor over, but it can have far reaching
performance implications. Imagine that you have a plot of land. On one side of
your plot of land is a box of nails. How long would it take you to do anything
if you only used one nail at a time and kept returning to the box of nails
every time you needed to use another one? This sort of thing might not be bad for
small tasks like hanging a picture on the wall, but it would become incredibly
time consuming if you were trying to build an addition for your house. The same
thing is happening within your T-SQL. During query evaluation, SQL Server must
evaluate the output of the scalar function once per row. This could require
additional disk access and potentially slow down the query.
Scalar
functions, when used appropriately, can be incredibly effective. Just be
careful to evaluate their use on datasets similar to the ones you will see in
production before you make the decision to use them; they have some
characteristics that may cause undesirable side effects. If your scalar UDF
needs to work on many rows, one solution is to rewrite it as a table-valued
function, as will be demonstrated a little later.
Issue 2:
Scalar functions
in the WHERE Clause
Using a
scalar function in the WHERE clause can also have disastrous effects on performance.
Although the symptoms are the same (row-by-row execution), the cause is
different. Consider the call to the built-in scalar function, DATEADD, in
Listing 5.
SELECT *
FROM Sales.SalesOrderHeader
AS soh
WHERE DATEADD(mm, 12, soh.OrderDate) < GETDATE()
Poor Usage of Where Clause
This
code will result in a full scan of the Sales.SalesOrderHeader table because SQL Server can’t use any index on the OrderDatecolumn. Instead, SQL Server has to scan every row in the
table and apply the function to each row. A better, more efficient way to write
this particular query would be to move the function, as shown in Listing 6.
SELECT *
FROM Sales.SalesOrderHeader
AS soh
WHERE soh.OrderDate
< DATEADD(mm, -12, GETDATE())
Better
use of a function in the WHERE clause
Optimizing
the use of a function in the WHERE clause isn’t always that
easy, but in many occasions this problem can be alleviated through the use of
careful design, a computed column, or a view.
Issue 3: (Kalen
Delaney)
I discovered some bad behavior shortly after SQL Server 2000
came out when I was contracted to do some performance tuning and troubleshooting for an insurance company in Atlanta. One
of the developers had written a scalar UDF to mask social security numbers in a
report they needed to run frequently. The function took a social security
number as a character string input value, replaced some of the digits with
special characters, and returned the masked value as output.
When we ran the report with only about 10,000 customer rows, it took more than
20 minutes to run. Something wasn’t right. I started a trace to see what
statements in the report were taking the most time and saw 10,000 separate
calls to the masking function. For each row, the trace showed SQL Server
calling the function, replacing the string values, and returning the result,
just as if we had written code with a cursor to process the data one row at a
time. If I replaced the function call with the actual code used to mask the
characters, the trace showed a single call to the function and the report
finished in under a minute.
For example, here’s a scalar UDF that takes a social security number (nine
digits and two hyphens) as input and replaces the first five digits with X’s.
CREATE FUNCTION
dbo.fnMaskSSN (@ssn char(11))
RETURNS char(11)
AS BEGIN
SELECT @SSN = 'xxx-xx-' + right(@ssn,4)
RETURN @SSN
END;
To test it, you could use any table that has a social
security number column, including the authors table from the old sample
database pubs.
SELECT dbo.fnMaskSSN(au_id), au_lname, au_fname FROM authors;
To improve the performance, you would need to forget about
the UDF, and replace the SELECT with the function definition:
SELECT 'xxx-xx-' + right(au_id,4), au_lname, au_fname FROM authors;
Of course, with 23 rows in the authors table, you wouldn’t
see the performance advantage, but if you ran a trace you would see a
difference in the amount of work that SQL Server was doing behind the scenes.
Although the developer wasn’t happy to have his function ignored, the DBA, who
was ultimately responsible for system performance, was delighted.
Behavior with NULL input:
“I’m
creating a scalar function that will be used against a column that contains
multiple NULL values. Is there a way to stop the function from being executed
when a NULL is passed in as a parameter value?”
Yes.
In fact, doing so is actually a very straightforward process, at least for
scalar functions. You simply include
the RETURNSNULL ON NULL INPUT option in
your WITH clause, as shown in the following example (option
highlighted):
CREATE FUNCTION
dbo.searchString
(
@string NVARCHAR(MAX),
@keyword NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
DECLARE @retValue NVARCHAR(MAX)
SET @retValue =
'Input Valid…'
RETURN @retValue
END
GO
By default, when you call a scalar function, the database engine
executes the function body whether or not a NULL is passed in as a
parameter value. However, by including the RETURNS NULL ON NULL INPUT option,
the database engine will not execute the function body when a NULL value
is passed in. For example, the following SELECT statement returns a NULL value:
SELECT dbo.searchString(N'SqlAndMe', N'Test'), -- Input Valid…
SELECT dbo.searchString(N'SqlAndMe', N''), -- Input Valid…
SELECT dbo.searchString(N'SqlAndMe', NULL), -- NULL
SELECT dbo.searchString(NULL, N'Test') -- NULL
GO
When the database engine sees the NULL input value, it
simply returns NULL without processing the function body. If your function supports multiple input
parameters, the database engine returns NULL if NULL is
passed into any one of those parameters and does not execute the function body.
However, you cannot use
the RETURNS NULL ON NULL INPUT option for a
table-valued function. Because a table-value function returns a full result set
(table), it is possible for the function to return data even if a parameter
value is NULL. As such, the option cannot be practically applied to a
table-valued function.
Users
of SQL Server 2000 and earlier are out of luck, though, as this feature was
introduced in SQL Server 2005.
CROSS APPLY:
I created a table-valued function that I want to apply to a column in each row
returned by the query. However, when I try to join the function to the target
table in the query’s FROM clause, I receive an error. Is there an effective way
to invoke the function for each row in the result set?
There is as long as you’re using SQL Server 2005 or later.
Starting with SQL Server 2005, you’ve been able to use the APPLY operator
to join one or more tables to a table-valued function in order to invoke that
function against each row in the result set. Prior to SQL Server 2005, you had
to come up with a complex workaround to achieve this.
The best way to understand how the operator works is to look
at an example. The following T-SQL creates a table-valued function that returns
the total number of items sold for each sale listed in the SalesOrderDetail table of
the AdventureWorks2012database:
USE
AdventureWorks2012;
GO
IF OBJECT_ID(N'dbo.ifGetTotalItems', N'IF') IS NOT NULL
DROP FUNCTION
dbo.ifGetTotalItems;
GO
CREATE FUNCTION
dbo.ifGetTotalItems (@OrderID INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT SUM(OrderQty) AS TotalItems
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = @OrderID
GROUP BY SalesOrderID
);
The function takes as an argument the SalesOrderID value that identifies the sale. In this
case, the function returns only a single row with one column, but you can just
as easily create a function that returns multiple rows. The simplest way to
verify that the function is working as you expect is to run a simple SELECT statement that calls the function and
passes in a SalesOrderIDvalue:
SELECT TotalItems FROM dbo.ifGetTotalItems(43659);
For this example, we pass in 43659 the parameter value. The function then
returns 26. However, all we’ve so far is to demonstrate that the
function works as we expect. Let’s create a more complex SELECT statement that applies the function to
each row returned from the SalesOrderHeader table. That’s where the APPLY operator comes in, as shown in the
following example:
SELECT s.SalesOrderID, s.OrderDate, s.SalesPersonID,
f.TotalItems
FROM Sales.SalesOrderHeader
s
CROSS APPLY dbo.ifGetTotalItems(s.SalesOrderID) f
ORDER BY SalesOrderID;
Notice that after we specify the SalesOrderHeader table in the FROM clause, we then include the CROSS APPLY keywords, following by the
function, with the SalesOrderID column passed in as the
parameter value.
The APPLY operator takes two forms: CROSS APPLY and OUTER APPLY. The CROSS APPLY combination returns rows
from the primary table (in this case, SalesOrderHeader) only
if they produce a result set from the table-valued function. The OUTERAPPLY combination returns all
rows from the primary table. In this case, both forms of APPLY return the same number of rows.
As you can see, the results include the TotalItems column, which is the number of items
associated with that sale. In other words, we were able to apply the
table-valued function to each row returned from the SalesOrderHeader table. If our function had returned multiple rows, the result set would have
included that number of rows for each row returned by the function. For
example, if the function always returned three rows for each SalesOrderID value, our result set would include
three times the number of rows than it currently does.
Default Value for Input parameter:
“I’m creating a
user-defined function and want to specify a default value for an input
parameter. Is that possible?”
Yes, it is possible and fairly easy to do. When defining the
parameter, include the default value, along with the equal sign, as shown the
following example (highlighted):
USE AdventureWorks2012;
GO
IF OBJECT_ID(N'dbo.GetPersonTypeCount', N'IF') IS NOT NULL
DROP FUNCTION dbo.GetPersonTypeCount;
GO
CREATE FUNCTION dbo.GetPersonTypeCount
(@type NCHAR(2) = 'IN')
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT COUNT(*) AS PersonCount
FROM Person.Person
WHERE
PersonType = @type
GROUP BY PersonType
);
GO
In this case, we’ve merely specified that the default value
for the @type parameter is IN. However, if we want to use the default value
when calling the view, we must specify the default keyword as the parameter value, as shown
in the following example:
SELECT * FROM dbo.GetPersonTypeCount(default);
The function will now use the IN default value and return a value of 18484. That said, even though we’ve defined a
default value, we can still specify that value when calling the function:
SELECT * FROM dbo.GetPersonTypeCount('in');
Or we can specify a different value, as we would if no
default had been specified:
SELECT * FROM
dbo.GetPersonTypeCount('em');
Parameterized View Vs Function:
“I’ve heard you can use a function to parameterize a view,
but I don’t see how you can incorporate a function into a view definition in
such a way to support parameters. Can you explain how that is done?”
Using a function to parameterize a view has little to do with
the view definition itself. It merely means you’re creating a function that
either duplicates the logic of the view or calls the view within the function.
In either case, you use a parameter to qualify the function’s SELECT statement.
For example, suppose we create the following view to retrieve
data about the number of employees per job title:
USE AdventureWorks2012;
GO
IF OBJECT_ID(N'dbo.JobData', N'V') IS NOT NULL
DROP VIEW dbo.JobData;
GO
CREATE VIEW dbo.JobData
WITH SCHEMABINDING
AS
SELECT
JobTitle, COUNT(*) AS TotalEmps
FROM
HumanResources.Employee
GROUP BY JobTitle;
GO
As you can see, the view is very straightforward. The SELECT statement groups the data
by the JobTitle column and retrieves a count for each group. You can, of
course, create a view that is far more complex than this one, but what we’ve
done here is enough to demonstrate how this all works.
Once you’ve created the view, you can test it by running a
simple SELECT statement, similar to the following:
SELECT * FROM dbo.JobData;
Not surprisingly, the statement returns all rows and columns
returned by the view.
As with any SELECT statement that retrieves data
from a view, we can further refine our SELECT statement by including
the logic necessary to return the results we need, as shown in the following
example:
SELECT TotalEmps FROM
dbo.JobData
WHERE JobTitle =
'Buyer';
In this case, we’ve merely specified a column in
the SELECT list and added a WHERE clause that limits the
results to those rows in which the JobTitle value equals Buyer.
Now the statement returns only a value of 9 because that’s how many
employees have that title.
Rather than creating a view and then qualifying
the SELECT statements that call the view, we can instead create a
table-valued function that incorporates the view’s logic, but also provides the
ability to qualify the results through a parameter, as shown in the following
example:
USE AdventureWorks2012;
GO
IF OBJECT_ID(N'dbo.GetJobData', N'IF') IS NOT NULL
DROP FUNCTION
dbo.GetJobData;
GO
CREATE FUNCTION
dbo.GetJobData (@title NVARCHAR(25))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT COUNT(*) AS TotalEmps
FROM HumanResources.Employee
WHERE JobTitle =
@title
GROUP BY JobTitle
);
GO
Notice that the function’s SELECT statement is
similar to that of the view’s except that we also include
a WHERE clause that compares the JobTitle column to
the @title input parameter. When you call the function, you simply
pass in the job title as an argument:
SELECT * FROM dbo.GetJobData('Buyer');
As to be expected, the SELECT statement returns a
value of 9. Chances are, however, you’ll want to incorporate the function
in a more complex query. In the following example, we use
the APPLY operator to join the Person and Employee tables
to the GetJobData function:
SELECT
p.FirstName + ' ' + p.LastName AS FullName,
e.JobTitle,
f.TotalEmps
FROM
HumanResources.Employee
e
INNER JOIN
Person.Person p
ON e.BusinessEntityID
= p.BusinessEntityID
CROSS APPLY
dbo.GetJobData(e.JobTitle) f;
For each row returned from the
joined Person and Employee tables,
the GetJobData function is applied to that row, based on the value of
the JobTitle column. The results will then include
a TotalEmps column, which will provide the total number of employees
who share the same title as the person listed in that row. The following table
provides a partial list of employees, their job titles, and the number of
people who share that title.
UDF Vs View
User-defined
functions that return a table data type can be powerful alternatives
to views. These functions are referred to as table-valued functions. A
table-valued user-defined function can be used where table or view expressions
are allowed in Transact-SQL queries. While views are limited to a single SELECT
statement, user-defined functions can contain additional statements that allow
more powerful logic than is possible in views.
UDF Vs SP:
A
table-valued user-defined function can also replace stored procedures that
return a single result set. The table returned by a user-defined function can
be referenced in the FROM clause of a Transact-SQL statement, but stored
procedures that return result sets cannot.
ProductCostDifference function will be executed once for each of the 504 rows in
the Production.Product table. This leads to an increase in disk access, CPU
utilization, and memory utilization.When we ran the report with only about 10,000 customer rows, it took more than 20 minutes to run. Something wasn’t right. I started a trace to see what statements in the report were taking the most time and saw 10,000 separate calls to the masking function. For each row, the trace showed SQL Server calling the function, replacing the string values, and returning the result, just as if we had written code with a cursor to process the data one row at a time. If I replaced the function call with the actual code used to mask the characters, the trace showed a single call to the function and the report finished in under a minute.
For example, here’s a scalar UDF that takes a social security number (nine digits and two hyphens) as input and replaces the first five digits with X’s.
No comments:
Post a Comment