Building User-DefinedFunctions IN THIS CHAPTER Creating scalar functions Replacing views with inline table-valued functions Using complex code within multi-statement table-valued functio
Trang 1Proc A Raiserror Select from A
#temp table
(insert…exec) (try…catch)
Proc B
DML Select
exec
exec
Result set from B
Raiserror from B
Output Parameters
Return
Return
Result set from B Raiserror Output Parameters
Best Practice
With every returned record set, SQL Server will, by default, also send a message stating the number of
rows affected or returned Not only is this a nuisance, but I have found in my informal testing that it
can slow a query by up to 17 percent depending on the query’s complexity
Therefore, get into the habit of beginning every stored procedure with the following code:
CREATE PROC MyProc
AS
SET NOCOUNT ON;
Summary
Using stored procedures is a way to save and optimize batches Stored procedures are compiled and
stored in memory the first time they are executed No method is faster at executing SQL commands, or
more popular for moving the processing close to the data Like a batch, a stored procedure can return a
record set by simply executing aSELECTcommand
The next chapter covers user-defined functions, which combine the benefits of stored procedures with
the benefits of views at the cost of portability
Trang 2Building User-Defined
Functions
IN THIS CHAPTER Creating scalar functions Replacing views with inline table-valued functions Using complex code within multi-statement table-valued functions to generate a result set
SQL Server 2000 introduced user-defined functions (UDFs), and the SQL
Server community was initially slow to adopt them Nevertheless, UDFs
were my personal favorite new feature in SQL Server 2000, and I still use
them frequently
The community discovered that UDFs can be used to embed complex T-SQL
logic within a query, and problems that were impossible or required cursors
could now be solved with UDFs The result is that UDFs have become a favorite
tool in the toolbox of any serious SQL Server database developer
The benefits of UDFs can be easily listed:
■ UDFs can be used to embed complex logic within a query This is huge
I’ve solved several nasty problems using user-defined functions
■ UDFs can be used to create new functions for complex expressions
■ UDFs offer the benefits of views because they can be used within
theFROMclause of aSELECTstatement or an expression, and they
can be schema-bound In addition, user-defined functions can accept
parameters, whereas views cannot
■ UDFs offer the benefits of stored procedures because they are compiled
and optimized in the same way
The chief argument against developing with user-defined functions has to do with
potential performance issues if they’re misused Any function, user-defined or
system that must be executed for every row in aWHEREclause will cripple
performance (see the sidebar on algebra in Chapter 8, ‘‘Introducing Basic
Query Flow.’’)
User-defined functions come in three distinct types (as shown in Figure 25-1.)
Management Studio groups inline table-valued functions with multi-statement
table-valued functions:
■ Scalar functions that return a single value
Trang 3User-defined functions haven’t changed much since they were introduced in SQL Server 2000 If you’re
upgrading to SQL Server 2008 directly from SQL Server 2000, then it’s worth knowing that the APPLY
keyword, covered later in this chapter, was added in SQL Server 2005
Microsoft system functions are changing When they were introduced in SQL Server 2000, system functions
were all prefixed with a double colon, such as ::fn_SystemFunction The double colon has been
deprecated and will be disabled in a future version of SQL Server Instead, system functions are now in the
sys.schema — for example, sys.fn_SystemFunction
FIGURE 25-1
Management Studio’s Object Explorer lists all the user-defined functions within a database, organized
by table-valued and scalar-valued
Trang 4Andrew Novick, who regularly presents about UDFs at user groups and SQL conferences,
has compiled a great resource of sample UDFs His website is www.novicksoftware.com
Scalar Functions
A scalar function is one that returns a single specific value The function can accept multiple parameters,
perform a calculation, and then return a single value For example, a scalar function could accept three
parameters, perform a calculation, and return the answer
Within the code of a scalar function, the value is passed back through the function by means of a
RETURNcommand Every possible codepath in the user-defined function should conclude with
aRETURNcommand
Scalar user-defined functions may be used within any expressions in SQL Server, even
expres-sions within check constraints (although I don’t recommend scalar function within check
constraints because that extends the duration of the transaction and it is difficult to locate and
maintain later)
Limitations
The scalar function must be deterministic, meaning it must repeatedly return the same value for
the same input parameters For this reason, nondeterministic functions — such asnewid()and
rand()— are not allowed within scalar functions Writing a UDF to return a random row is out of the
question
User-defined scalar functions are not permitted to update the database or callDBCCcommands, with
the single exception that they may update table variables They cannot return BLOB (binary large
object) data such astext,ntext,timestamp, andimagedata-type variables, nor can they return
table variables orcursordata types For error handling, UDFs may not includeTRY .CATCHor
RAISERROR
A user-defined function may call other user-defined functions nesting up to 32 levels deep, or it can call
itself recursively up to 32 levels deep before it blows up
Creating a scalar function
User-defined functions are created, altered, or dropped with the same DDL commands used for other
objects, although the syntax is slightly different to allow for the return value:
CREATE FUNCTION FunctionName (InputParameters)
RETURNS DataType
AS
BEGIN;
Code;
RETURN Expression;
END;
Trang 5eter includes a default value:
CREATE FUNCTION dbo.fsMultiply (@A INT, @B INT = 3)
RETURNS INT AS
BEGIN;
RETURN @A * @B;
END;
go SELECT dbo.fsMultiply (3,4),
dbo.fsMultiply (7, DEFAULT);
Result:
While I’m not a stickler for naming conventions (as long as they’re consistent), I can under-stand the practice of prefacing user-defined functions with an f
For a more complex scalar user-defined function, thefGetPricefunction from theOBXKitessample
database returns a single result via an output parameter It’s just a variation of thepGetPricestored
procedure Both the stored procedure and function determine the correct price for any given date and
for any customer discount Because the task returns a single value, calculating the price is a prime
candi-date for a scalar user-defined function As a function, it can be plugged into any query, whereas a stored
procedure is more difficult to use as a building block in other code
The function uses the same internal code as the stored procedure, except that the@CurrPriceis
passed back through the finalRETURNinstead of an output variable The function uses a default value
of NULLfor the contact code Here is the code for thefsGetPriceuser-defined scalar function:
CREATE FUNCTION fsGetPrice (
@Code CHAR(10),
@PriceDate DATETIME,
@ContactCode CHAR(15) = NULL) RETURNS MONEY
AS BEGIN;
DECLARE @CurrPrice MONEY ; DECLARE @DiscountPercent NUMERIC (4,2);
set the discount percent
Trang 6if no customer lookup then it’s zilch discount
SELECT @DiscountPercent = CustomerType.DiscountPercent
FROM dbo.Contact
JOIN dbo.CustomerType
ON contact.CustomerTypeID =
CustomerType.CustomerTypeID WHERE ContactCode = @ContactCode;
IF @DiscountPercent IS NULL
SET @DiscountPercent = 0;
SELECT @CurrPrice = Price * (1-@DiscountPercent)
FROM dbo.Price
JOIN dbo.Product
ON Price.ProductID = Product.ProductID WHERE Code = @Code
AND EffectiveDate =
(SELECT MAX(EffectiveDate)
FROM dbo.Price JOIN dbo.Product
ON Price.ProductID = Product.ProductID WHERE Code = @Code
AND EffectiveDate <= @PriceDate);
RETURN @CurrPrice;
END;
Calling a scalar function
Scalar functions may be used anywhere within any expression that accepts a single value
User-defined scalar functions must always be called by means of at least a two-part name (owner.name) The
following script demonstrates calling thefGetPrice()function withinOBXKites:
USE OBXKites;
SELECT dbo.fsGetPrice(’1006’,CURRENT_TIMESTAMP,DEFAULT),
dbo.fsGetPrice(’1001’,’5/1/2001’,NULL);
Result:
The user-defined scalar function dbo.fTitleCase is created in Chapter 9, ‘‘Data Types,
Expressions, and Scalar Functions,’’ and is available on www.sqlserverbible.com
Inline Table-Valued Functions
The second type of user-defined function, the inline table-valued function, is very similar to a view Both
are wrapped for a storedSELECTstatement An inline table-valued user-defined function retains the
benefits of a view, and adds parameters As with a view, if the SELECTstatement is updateable, then
the function will be updateable
Trang 7RETURN (Select Statement);
The following inline table-valued user-defined function is functionally equivalent to thevEventList
view created in Chapter 14, ‘‘Projecting Data Through Views.’’
USE CHA2;
go CREATE FUNCTION ftEventList () RETURNS Table
AS RETURN(
SELECT dbo.CustomerType.Name AS Customer, dbo.Customer.LastName, dbo.Customer.FirstName, dbo.Customer.Nickname,
dbo.Event_mm_Customer.ConfirmDate, dbo.Event.Code, dbo.Event.DateBegin, dbo.Tour.Name AS Tour,
dbo.BaseCamp.Name, dbo.Event.Comment FROM dbo.Tour
INNER JOIN dbo.Event
ON dbo.Tour.TourID = dbo.Event.TourID INNER JOIN dbo.Event_mm_Customer
ON dbo.Event.EventID = dbo.Event_mm_Customer.EventID INNER JOIN dbo.Customer
ON dbo.Event_mm_Customer.CustomerID
= dbo.Customer.CustomerID LEFT OUTER JOIN dbo.CustomerType
ON dbo.Customer.CustomerTypeID
= dbo.CustomerType.CustomerTypeID INNER JOIN dbo.BaseCamp
ON dbo.Tour.BaseCampID = dbo.BaseCamp.BaseCampID);
Calling an inline table-valued function
To retrieve data throughftEventList, call the function within theFROMportion of aSELECT
statement:
SELECT LastName, Code, DateBegin
FROM dbo.ftEventList();
Result (abridged):
Trang 8LastName Code DateBegin
- -
-Anderson 01-003 2001-03-16 00:00:00.000
.
Using parameters
An advantage of inline table-valued functions over views is the function’s ability to include parameters
within the pre-compiledSELECTstatement Views, conversely, do not include parameters, and
restrict-ing the result at runtime is typically achieved by addrestrict-ing aWHEREclause to theSELECTstatement that
calls the view
The following examples compare adding a restriction to the view to using a function parameter The
following view returns the current price list for all products:
USE OBXKites;
go
CREATE VIEW vPricelist
AS
SELECT P.Code, Price.Price
FROM dbo.Price
JOIN dbo.Product P
ON Price.ProductID = P.ProductID
WHERE EffectiveDate =
(SELECT MAX(EffectiveDate)
FROM dbo.Price
WHERE ProductID = P.ProductID
AND EffectiveDate <= CURRENT_TIMESTAMP);
To retrieve the current price for a single product, the callingSELECTstatement adds aWHERE-clause
restriction when calling the view:
SELECT *
FROM vPriceList
WHERE = ‘1001’;
Result:
-
SQL Server internally creates a new SQL statement fromvPricelistand the callingSELECT
state-ment’sWHERE-clause restriction and then generates a query execution plan
Trang 9SELECT Code, Price.Price FROM dbo.Price
JOIN dbo.Product P
ON Price.ProductID = P.ProductID WHERE EffectiveDate =
(SELECT MAX(EffectiveDate) FROM dbo.Price
WHERE ProductID = P.ProductID
AND EffectiveDate <= @PriceDate) AND (Code = @Code
OR @Code IS NULL) );
If the function is called with default code, then the price for the entered date is returned for all
products:
SELECT * FROM dbo.ftPriceList(DEFAULT, ‘20020220’);
Result:
-
If a product code is passed in the first input parameter, then the pre-compiledSELECTstatement within
the function returns the single product row:
SELECT * FROM dbo.ftPriceList(’1001’, ‘2/20/2002’);
Result:
-
Correlated user-defined functions
TheAPPLYcommand may be used with a table-valued user-defined function so that the UDF accepts a
different parameter value for each corresponding row being processed by the main query
Trang 10Back in the SQL Server 2000 days, not having this capability was a serious limitation that caused me
a considerable amount of time to work around, so I’m pleased to see that Microsoft added theAPPLY
function in SQL Server 2005
TheAPPLYcommand has two forms The most common form, the CROSS APPLY, has a confusing
name because it operates more like an inner join than a cross join TheCROSS APPLYcommand will
join data from the main query with any table-valued data sets from the user-defined function If no
data is returned from the UDF, then the row from the main query is also not returned, as shown in the
following example:
USE CHA2;
go
CREATE FUNCTION ftEventList2 (@CustomerID INT)
RETURNS Table
AS
RETURN(
SELECT dbo.CustomerType.Name AS Customer,
dbo.Customer.LastName, dbo.Customer.FirstName,
dbo.Customer.Nickname,
dbo.Event_mm_Customer.ConfirmDate, dbo.Event.Code,
dbo.Event.DateBegin, dbo.Tour.Name AS Tour,
dbo.BaseCamp.Name, dbo.Event.Comment
FROM dbo.Tour
INNER JOIN dbo.Event
ON dbo.Tour.TourID = dbo.Event.TourID INNER JOIN dbo.Event_mm_Customer
ON dbo.Event.EventID = dbo.Event_mm_Customer.EventID INNER JOIN dbo.Customer
ON dbo.Event_mm_Customer.CustomerID
= dbo.Customer.CustomerID LEFT OUTER JOIN dbo.CustomerType
ON dbo.Customer.CustomerTypeID
= dbo.CustomerType.CustomerTypeID INNER JOIN dbo.BaseCamp
ON dbo.Tour.BaseCampID = dbo.BaseCamp.BaseCampID
WHERE Customer.CustomerID = @CustomerID
);
SELECT C.LastName, Code, DateBegin, Tour
FROM Customer C
CROSS APPLY ftEventList2(C.CustomerID)
ORDER BY C.LastName;
Result:
- - -
-Anderson 01-003 2001-03-16 00:00:00.000 Amazon Trek
Anderson 01-006 2001-07-03 00:00:00.000 Bahamas Dive