A Scalar user-defined function returns one of the scalar data types. Text, ntext, image and timestamp data types are not supported. These are the type of user-defined functions that most developers are used to in other programming languages. You pass in 0 to many parameters and you get a return value. Below is an example that is based in the data found in the NorthWind Customers Table.
CREATE FUNCTION whichContinent (@Country nvarchar(15)) RETURNS varchar(30) AS BEGIN declare @Return varchar(30) select @return = case @Country when 'Argentina' then 'South America' when 'Belgium' then 'Europe' when 'Brazil' then 'South America' when 'Canada' then 'North America' when 'Denmark' then 'Europe' when 'Finland' then 'Europe' when 'France' then 'Europe' else 'Unknown' end return @return endBecause this function returns a scalar value of a varchar(30) this function could be used anywhere a varchar(30) expression is allowed such as a computed column in a table, view, a T-SQL select list item. Below are some of the examples that I was able to use after creating the above function definition. Note that I had to reference the dbo in the function name.
print dbo.WhichContinent('USA') select dbo.WhichContinent(Customers.Country), customers.* from customers create table test (Country varchar(15), Continent as (dbo.WhichContinent(Country))) insert into test (country) values ('USA') select * from test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Country Continent --------------- ------------------------------ USA North AmericaStored procedures have long given us the ability to pass parameters and get a value back, but the ability to use it in such a variety of different places where you cannot use a stored procedure make this a very powerful database object. Also notice the logic of my function is not exactly brain surgery. But it does encapsulate the business rules for the different continents in one location in my application. If you were to build this logic into T-SQL statements scattered throughout your application and you suddenly noticed that you forgot a country (like I missed Austria!) you would have to make the change in every T-SQL statement where you had used that logic. Now, with the SQL Server User-Defined Function, you can quickly maintain this logic in just one place.
How do I create and use an Inline Table-Value User-Defined Function?An Inline Table-Value user-defined function returns a table data type and is an exceptional alternative to a view as the user-defined function can pass parameters into a T-SQL select command and in essence provide us with a parameterized, non-updateable view of the underlying tables.
CREATE FUNCTION CustomersByContinent (@Continent varchar(30)) RETURNS TABLE AS RETURN SELECT dbo.WhichContinent(Customers.Country) as continent, customers.* FROM customers WHERE dbo.WhichContinent(Customers.Country) = @Continent GO SELECT * from CustomersbyContinent('North America') SELECT * from CustomersByContinent('South America') SELECT * from customersbyContinent('Unknown')Note that the example uses another function (WhichContinent) to select out the customers specified by the parameter of this function. After creating the user-defined function, I can use it in the FROM clause of a T-SQL command unlike the behavior found when using a stored procedure which can also return record sets. Also note that I do not have to reference the dbo in my reference to this function. However, when using SQL Server built-in functions that return a table, you must now add the prefix :: to the name of the function.
Example from Books Online: Select * from ::fn_helpcollations()
How do I create and use a Multi-statement Table-Value User-Defined Function?A Multi-Statement Table-Value user-defined function returns a table and is also an exceptional alternative to a view as the function can support multiple T-SQL statements to build the final result where the view is limited to a single SELECT statement. Also, the ability to pass parameters into a T-SQL select command or a group of them gives us the capability to in essence create a parameterized, non-updateable view of the data in the underlying tables. Within the create function command you must define the table structure that is being returned. After creating this type of user-defined function, I can use it in the FROM clause of a T-SQL command unlike the behavior found when using a stored procedure which can also return record sets.
CREATE FUNCTION dbo.customersbycountry ( @Country varchar(15) ) RETURNS @CustomersbyCountryTab table ( [CustomerID] [nchar] (5), [CompanyName] [nvarchar] (40), [ContactName] [nvarchar] (30), [ContactTitle] [nvarchar] (30), [Address] [nvarchar] (60), [City] [nvarchar] (15), [PostalCode] [nvarchar] (10), [Country] [nvarchar] (15), [Phone] [nvarchar] (24), [Fax] [nvarchar] (24) ) AS BEGIN INSERT INTO @CustomersByCountryTab SELECT [CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [PostalCode], [Country], [Phone], [Fax] FROM [Northwind].[dbo].[Customers] WHERE country = @Country DECLARE @cnt INT SELECT @cnt = COUNT(*) FROM @customersbyCountryTab IF @cnt = 0 INSERT INTO @CustomersByCountryTab ( [CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [PostalCode], [Country], [Phone], [Fax] ) VALUES ('','No Companies Found','','','','','','','','') RETURN END GO SELECT * FROM dbo.customersbycountry('USA') SELECT * FROM dbo.customersbycountry('CANADA') SELECT * FROM dbo.customersbycountry('ADF') What are the benefits of User-Defined Functions?The benefits to SQL Server User-Defined functions are numerous. First, we can use these functions in so many different places when compared to the SQL Server stored procedure. The ability for a function to act like a table (for Inline table and Multi-statement table functions) gives developers the ability to break out complex logic into shorter and shorter code blocks. This will generally give the additional benefit of making the code less complex and easier to write and maintain. In the case of a Scalar User-Defined Function, the ability to use this function anywhere you can use a scalar of the same data type is also a very powerful thing. Combining these advantages with the ability to pass parameters into these database objects makes the SQL Server User-Defined function a very powerful tool.
SummarySo, if you have ever wanted to use the results of a stored procedure as part of a T-SQL command, use parameterized non-updateable views, or encapsulate complex logic into a single database object, the SQL Server 2000 User-Defined function is a new database object that you should examine to see if its right for your particular environment.
Exaples 2:
USE
TSQLUDFSGO
-- Get the first five authors and book titles.
SELECT
TOP 5dbo
.udf_Name_Full (au_fname, NULL, au_lname, NULL) as [Author] , Title FROM pubs..authors a INNER JOIN pubs..titleauthor ta ON a.au_id = ta.au_id INNER JOIN pubs..titles t ON ta.title_id = t.title_idGO
-- calling a function with constants and expressions
SELECT
dbo.udf_Name_Full('John', 'Jacob' , 'Jingleheimer-Schmitt', 'Esq.') as [My Name Too] , dbo.udf_Name_Full(LEFT('Andrew', 1) + '.' , LEFT('Stewart', 1) + '.' , 'Novick' , null) as [Author]GO
-- Move into the Northwind database to create the next function
USE
NorthwindGO
-- Drop any previous version of udf_EmpTerritoryCOUNT.
-- This batch isn't in the book. It's provided here in case you've
-- already created the function and want to recreate it.
IF
EXISTS (SELECT * FROM sysobjects WHERE name = 'udf_EmpTerritoryCOUNT' ) BEGIN PRINT 'Dropping existing udf_EmpTerritoryCOUNT' DROP FUNCTION udf_EmpTerritoryCOUNT ENDELSE
PRINT 'No previously existing version of udf_EmpTerritoryCOUNT'GO
-- These options should be set this way before creating any UDF
Set
Quoted_Identifier ONSet
ANSI_Warnings ONGO
-- Create script for udf_EmpTerritoryCOUNT
CREATE
FUNCTION dbo.udf_EmpTerritoryCOUNT (@EmployeeID
int -- ID of the employee)
RETURNS INT -- Number of territories assigned to the employeeAS
BEGIN DECLARE @Territories int -- Working Count SELECT @Territories = count(*) FROM EmployeeTerritories WHERE EmployeeID = @EmployeeID RETURN @TerritoriesEND
GO
-- EXEC permission is short for EXECUTE
GRANT
EXEC ON dbo.udf_EmpTerritoryCOUNT to PUBLICGO
-- Get the territory count for one employee
SELECT
dbo.udf_EmpTerritoryCOUNT (1) as [Nancy's Territory Count]GO
-- Get 3 the employees with the most territories
SELECT
TOP 3 LastName, FirstName , dbo.udf_EmpTerritoryCOUNT(EmployeeID) as Territories FROM Employees WHERE dbo.udf_EmpTerritoryCOUNT(EmployeeID) > 3 ORDER BY dbo.udf_EmpTerritoryCount(EmployeeID) descGO
USE
NorthwindGO
-- Drop any previous version of udf_EmpTerritoriesTAB.
-- This batch isn't in the book. It's provided here in case you've
-- already created the function and want to recreate it.
IF
EXISTS (SELECT * FROM sysobjects WHERE name = 'udf_EmpTerritoriesTAB' ) BEGIN PRINT 'Dropping existing udf_EmpTerritoriesTAB' DROP FUNCTION udf_EmpTerritoriesTAB ENDELSE
PRINT 'No previously existing version of udf_EmpTerritoriesTAB'GO
-- These options should be set this way before creating any UDF
Set
Quoted_Identifier ONSet
ANSI_Warnings ONGO
CREATE
FUNCTION dbo.udf_EmpTerritoriesTAB (@EmployeeID
int -- EmployeeID column)
RETURNS TABLE/*
* Returns a table of information about the territories assigned
* to an employee.
*
* Example:
select * FROM udf_EmpTerritoriesTAB(2)
****************************************************************/ P>
AS
RETURNSELECT
TOP 100 PERCENT WITH TIES -- TOP Makes ORDER BY OKet
.TerritoryID , t.TerritoryDescription as [Territory] , t.RegionID FROM EmployeeTerritories et LEFT OUTER JOIN Territories t ON et.TerritoryID = t.TerritoryID WHERE ET.EmployeeID = @EmployeeID ORDER BY t.TerritoryDescriptionGO
GRANT
SELECT ON dbo.udf_EmpTerritoriesTAB to [PUBLIC]GO
-- Get the EmployeeID of Andrew Fuller. Use it to get his territories.
DECLARE
@EmpID int -- Working copy of the EmployeeID columnSELECT
@EmpID = EmployeeID FROM Employees WHERE FirstName = 'Andrew' and LastName = 'Fuller'-- Now use @EmpID to get the list of territories
SELECT
TerritoryID, Territory FROM udf_EmpTerritoriesTAB(@EmpID)GO
-- Still in the Northwind database
USE
NorthwindGO
-- Get the EmployeeID of Andrew Fuller. Use it to get his regions
DECLARE
@EmpID int -- Working copy of the EmployeeID columnSELECT
@EmpID = EmployeeID FROM Employees WHERE FirstName = 'Andrew' and LastName = 'Fuller'SELECT
r.RegionID , r.RegionDescription as [Region] FROM udf_EmpTerritoriesTAB (@EmpID) t LEFT OUTER JOIN Region r ON t.RegionID = r.RegionID GROUP BY r.RegionID, r.RegionDescriptionGO
-- Multi-statement Tabled Valued UDFs
-- Work in the NorthWind database
USE
NorthwindGO
-- Drop any previous version of udf_DT_MonthsTAB.
-- This batch isn't in the book. It's provided here in case you've
-- already created the function and want to recreate it.
IF
EXISTS (SELECT * FROM sysobjects WHERE name = 'udf_DT_MonthsTAB' ) BEGIN PRINT 'Dropping existing udf_DT_MonthsTAB' DROP FUNCTION udf_DT_MonthsTAB ENDELSE
PRINT 'No previously existing version of udf_DT_MonthsTAB'GO
Set
Quoted_Identifier ONSet
ANSI_Warnings ONGO
-- Create the udf_DT_MonthsTAB function
CREATE
FUNCTION dbo.udf_DT_MonthsTAB (@StartDT
datetime -- Date in the 1st month , @EndDT datetime -- Date in the last month)
RETURNS @Months TABLE ([Year]
smallint -- Year Number , [Month] smallint -- Month number 1-12 , [Name] varchar(9) -- Month name January,... , Mon char(3) -- Jan, Feb.. , StartDT datetime -- Date the month Starts , EndDT datetime -- EOD of last day of month , End_SOD_DT datetime -- SOD of Last day of month , StartJulian int -- Julian Start Date , EndJulian int -- Julian end date , NextMonStartDT datetime -- Start Date of Next Month )/*
* Returns a table of months that are between two dates including
* both end points. Each row has several ways to represent the
* month. The result table is intended to be used in reports that
* are reporting on activity in all months in a range.
*
* Example: -- to create a table of months in 2002
select * FROM dbo.udf_DT_MonBtwnTAB('2002-01-01', '2002-12-31')
****************************************************************/ P>
AS
BEGINDECLARE
@MonStrt datetime -- Start of Month for looping , @NxMonStDT datetime -- Start of Next Month , @Julian0 datetime -- Origin of Julian calendar-- Get the start of the first month
SET
@MonStrt = CONVERT(datetime, CONVERT(char(4), YEAR(@StartDT)) + '-' + CONVERT(VARCHAR(2), MONTH(@StartDT)) + '-01')SET
@Julian0 = CONVERT(datetime, '1900-01-01')WHILE
@MonStrt T color=#808080 size=2./FONT/FONTFONT size=2ShippedDate /FONTFONT color=#808080 size=2FONT color=#808080 size=2>= '1998-01-01' AND o.ShippedDate = '1998-01-01' AND o.ShippedDate