0% found this document useful (0 votes)
49 views7 pages

Microsoft T-SQL - User Defined Functions

UDFs are routines that perform calculations / computations and return a valu-scalar (singular) or a table. They can be embedded in queries, constraints, and computed columns. The code that defines a UDF may not cause side effects that affect the database state.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
49 views7 pages

Microsoft T-SQL - User Defined Functions

UDFs are routines that perform calculations / computations and return a valu-scalar (singular) or a table. They can be embedded in queries, constraints, and computed columns. The code that defines a UDF may not cause side effects that affect the database state.
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

Microsoft T-SQL: User-Defined Functions

User-Defined Functions Introduction


User-defined functions (UDFs) are routines that perform calculations/computations and return a valu-scalar (singular) or a table. UDFs can be embedded in queries, constraints, and computed columns. The code that defines a UDF may not cause side effects that affect the database state outside the scope of the functionthat is, the UDFs code is not allowed to modify data in tables or to invoke a function that has side effects (for example, RAND). In addition, the UDFs code can only create table variables and cannot create or access temporary tables. Also, the UDFs code is not allowed to use dynamic SQL. Scalar UDFs return a single (scalar) value. They can be specified where scalar expressions are allowedfor example, in a query, constraint, computed column, and so on. Scalar UDFs have several syntactical requirements: They must have a BEGIN/END block defining their body. They must be schema qualified when invoked (unless invoked as stored procedures with EXEC, as in EXEC myFunction 3, 4). They do not allow omitting optional parameters (ones that have default values) when invoked; rather, you must at least specify the DEFAULT keyword for those. T-SQL UDFs are typically faster than CLR UDFs when the main cost of their activity pertains to set-based data manipulation, as opposed to procedural logic and computations. In the functions header you specify its name, define the input parameters, and define the data type of the returned value. This code creates the fn_get_car name function, which accepts a model name as input and returns a string with the car name and car model.
IF OBJECT_ID('fn_get_car_name') IS NOT NULL

http://www.learn-with-video-tutorials.com

DROP FUNCTION fn_get_car_name; GO CREATE FUNCTION dbo.fn_get_car_name (@p_model VARCHAR(40)) RETURNS VARCHAR(100) AS BEGIN DECLARE @p_result VARCHAR(100); SET @p_result = (SELECT car + ' ' + model FROM cars WHERE model = @p_model); RETURN @p_result; END;

To test the function, run the following queries.


SELECT dbo.fn_get_car_name ('A-Class'); SELECT dbo.fn_get_car_name ('Navigator'); SELECT dbo.fn_get_car_name(model) FROM cars WITH (NOLOCK); EXEC fn_get_car_name @p_model='Navigator'; BEGIN DECLARE @name VARCHAR(100); SET @name = dbo.fn_get_car_name('Navigator'); print @name; END;

UDFs Used in DEFAULT Constraints


You can use scalar UDFs in DEFAULT constraints. The only limitation that you should be aware of is that a UDF cannot accept columns from the table as inputs when used in a DEFAULT constraint. This code creates a table called T1 and a UDF called fn_T1_getid, which returns the minimum missing ID in T1.
IF OBJECT_ID('T1') IS NOT NULL DROP TABLE T1; GO CREATE TABLE T1 ( id INT NOT NULL CONSTRAINT PK_ID PRIMARY KEY, col VARCHAR(5) ); IF OBJECT_ID('fn_T1_getid') IS NOT NULL DROP FUNCTION fn_T1_getid; GO CREATE FUNCTION dbo.fn_T1_getid() RETURNS INT AS BEGIN RETURN

http://www.learn-with-video-tutorials.com

CASE WHEN NOT EXISTS (SELECT * FROM T1 WHERE id = 1) THEN 1 ELSE (SELECT MIN(id) + 1 FROM T1 AS A WITH (NOLOCK) WHERE NOT EXISTS (SELECT * FROM T1 AS B WITH (NOLOCK) WHERE B.id = A.id + 1)) END; END;

This code adds a DEFAULT constraint to ID, which invokes the fn_T1_getid function.
ALTER TABLE T1 ADD DEFAULT(dbo.fn_T1_getid()) FOR id;

The following code inserts three rows, generating the keys 1, 2, and 3; deletes the row with the key 2; and inserts another row, generating the key 2.
INSERT INSERT INSERT DELETE INSERT INTO INTO INTO FROM INTO T1(col) VALUES('a'); T1(col) VALUES('b'); T1(col) VALUES('c'); T1 WHERE id = 2; T1(col) VALUES('d');

SELECT * FROM T1 WITH (NOLOCK);

Notice that key 2 was assigned to the row that was inserted last, because the row with the key 2 was previously deleted.

UDFs Used in PRIMARY KEY and UNIQUE Constraints


You can create a UNIQUE or PRIMARY KEY constraint on a computed column that invokes a UDF. Keep in mind that both constraints create a unique index under the covers. This means that the target computed column and the UDF it invokes must meet indexing guidelines. For example, the UDF must be schema bound (created with the SCHEMABINDING option); the computed column must be deterministic and precise or deterministic and persisted, and so on. You can find the details about indexing guidelines for computed columns and UDFs in SQL Server Books Online. This code attempts to add to T1 a computed column called col2 which invokes the fn_Add UDF and create a UNIQUE constraint on that column.
IF OBJECT_ID('fn_add') IS NOT NULL DROP FUNCTION fn_add; GO CREATE FUNCTION dbo.fn_add(@p_id INT) RETURNS INT AS BEGIN RETURN @p_id + 1; END; ALTER TABLE T1

http://www.learn-with-video-tutorials.com

ADD col2 AS dbo.fn_add(id) CONSTRAINT UK_T1_col2 UNIQUE;

The error occurs because the function doesnt meet one of the requirements for indexing, which says that the function must be schema bound. As you can see, the error message itself is not too helpful in indicating the cause of the error or in suggesting how to fix it. You need to realize that to fix the problem, you should alter the function by adding the SCHEMABINDING option. Try adding the computed column with the UNIQUE constraint again. This time your code runs successfully.
ALTER FUNCTION dbo.fn_add(@p_id INT) RETURNS INT WITH SCHEMABINDING AS BEGIN RETURN @p_id + 1; END; ALTER TABLE T1 ADD col2 AS dbo.fn_add(id) CONSTRAINT UK_T1_col2 UNIQUE;

Its a bit trickier when you try to create a PRIMARY KEY constraint on such a computed column. To see how this works, first drop the existing PRIMARY KEY from T1. Next, attempt to add another computed column called col3 with a PRIMARY KEY constraint.
ALTER TABLE T1 DROP CONSTRAINT PK_ID; ALTER TABLE T1 ADD col3 AS dbo.fn_add(id) CONSTRAINT PK_ID PRIMARY KEY;

The attempt fails, generating the error. You must explicitly guarantee that col2 never ends up with a NULL. You can achieve this by defining the column as PERSISTED and NOT NULL.
ALTER TABLE T1 ADD col3 AS dbo.fn_add(id) PERSISTED NOT NULL CONSTRAINT PK_ID PRIMARY KEY;

Inline Table-Valued UDFs


Inline table-valued UDFs are similar to views in the sense that their returned table is defined by a query specification. However, a UDFs query can refer to input parameters, while a view cannot. So you can think of an inline UDF as a parameterized view. SQL Server actually treats inline UDFs very similarly to views. The query processor replaces an inline UDF reference with its definition; in other words, the query processor expands the UDF definition and generates an execution plan accessing the underlying objects.

http://www.learn-with-video-tutorials.com

Unlike scalar and multistatement table-valued UDFs, you dont specify a BEGIN/END block in an inline UDFs body. All you specify is a RETURN clause and a query. In the functions header, you simply state that it returns a table.
IF OBJECT_ID('fn_get_cars') IS NOT NULL DROP FUNCTION fn_get_cars; GO CREATE FUNCTION dbo.fn_get_cars (@p_car_name VARCHAR(40)) RETURNS TABLE AS RETURN SELECT Car, Model FROM Cars WITH (NOLOCK) WHERE Car = @p_car_name;

Run the following queries.


SELECT * FROM dbo.fn_get_cars('Ferrari'); SELECT * FROM dbo.fn_get_cars('BMW');

Multistatement Table-Valued UDFs


A multistatement table-valued UDF is a function that returns a table variable. The function has a body with the sole purpose of populating the table variable. You develop a multistatement table-valued UDF when you need a routine that returns a table, and the implementation of the routine cannot be expressed as a single queryinstead, it requires multiple statements; for example, flow elements such as loops. A multistatement table-valued UDF is used in a similar manner to an inline table-valued UDF, but it cannot be a target of a modification statement; that is, you can use it only in the FROM clause of a SELECT query. Internally, SQL Server treats the two completely differently. An inline UDF is treated more like a viewas a table expression; a multistatement table-valued UDF is treated more like a stored procedure. As with other UDFs, a multistatement tablevalued UDF is not allowed to have side effects.
IF OBJECT_ID('fn_get_cars2') IS NOT NULL DROP FUNCTION fn_get_cars2; GO CREATE FUNCTION dbo.fn_get_cars2 (@p_car_name VARCHAR(40)) RETURNS @t_cars TABLE ( car VARCHAR(40), model VARCHAR(40) ) AS BEGIN INSERT INTO @t_cars SELECT Car, Model FROM Cars WITH (NOLOCK) WHERE Car = @p_car_name; DECLARE @p_letter VARCHAR(1);

http://www.learn-with-video-tutorials.com

SET @p_letter = UPPER(SUBSTRING(@p_car_name, 2, 1)); INSERT INTO @t_cars SELECT Car, Model FROM Cars WITH (NOLOCK) WHERE UPPER(SUBSTRING(Car, 1, 1)) = @p_letter; RETURN END;

This function will return all the cars, which name is equal to the p car name parameter, and all the cars, which name starts with the second letter of the p car name parameter. In this way, you can run the multistatement table-valued UDF.
SELECT * FROM dbo.fn_get_cars2('Ferrari'); SELECT * FROM dbo.fn_get_cars2('BMW');

Per-Row UDFs
Nondeterministic functions are functions that are not guaranteed to return the same output when invoked multiple times with the same input. When you invoke nondeterministic built-in functions in a query (such as RAND and GETDATE), those functions are invoked once for the whole query and not once per row. The only exception to this rule is the NEWID function, which generates a globally unique identifier (GUID). NEWID is the only nondeterministic built-in function that will be invoked once per row. To demonstrate this behavior of nondeterministic functions, run the following code. Suppose that you needed to invoke the RAND function for each row. You might have thought of invoking RAND from a UDF and then invoking the UDF in an outer query, knowing that a UDF is invoked once per row.
SELECT RAND(), GETDATE(), NEWID(), * FROM cars WITH (NOLOCK); IF OBJECT_ID('fn_rand') IS NOT NULL DROP FUNCTION fn_rand; GO CREATE FUNCTION fn_rand() RETURNS FLOAT AS BEGIN RETURN RAND(); END;

However, this attempt fails and produces the error. The error tells you that your function is not allowed to have side effects, and the RAND function does change an internal state (for use in a subsequent invocation of RAND). A back door allows you to implicitly invoke RAND from a UDF. Create a view that invokes RAND and query the view from the UDF.

http://www.learn-with-video-tutorials.com

CREATE VIEW v_rand AS SELECT RAND() AS R; CREATE FUNCTION fn_rand() RETURNS FLOAT AS BEGIN RETURN (SELECT R FROM v_rand); END; SELECT *, RAND() FROM cars WITH (NOLOCK); SELECT *, dbo.fn_rand() FROM cars WITH (NOLOCK);

We have achieved the desired effect, which shows the second query.

More lessons: http://www.learn-with-video-tutorials.com

http://www.learn-with-video-tutorials.com

You might also like