Create a Schema Bound UDF in SQL Server

In SQL Server, it’s usually a good idea to schema bind your user-defined functions (UDFs).

Schema binding your UDF will ensure that the underlying tables can’t be changed in a way that would affect your function. Without schema binding, the underlying tables or other objects could be modified or even deleted. Doing this could break the function.

To create a schema bound UDF, use the WITH SCHEMABINDING in your T-SQL code for creating the function. This applies whether the function is a scalar function or a table-valued function (TVF).

In any case, I’ve included examples of an inline TVF, a multi-statement TVF, and a scalar function.

Example 1 – Inline Table-Valued Function

Here’s an example of creating an inline TVF with schema binding:

CREATE FUNCTION dbo.udf_CatsByName_ITVF( @CatName varchar(70) )
    RETURNS TABLE
    WITH SCHEMABINDING
AS
RETURN (
    SELECT 
        CatId,
        CatName,
        Phone
    FROM dbo.Cats
    WHERE CatName = @CatName
    );

GO

Notice I used the two-part name when referencing the table in my query (I used dbo.Cats when referencing the table, instead of just Cats). Doing this is a requirement for schema binding an object. If you try to schema bind an object without using two-part names you’ll get an error.

Now that I’ve schema bound my function, if I try to drop the table referenced in its definition, I get an error:

DROP TABLE Cats;

Result:

Msg 3729, Level 16, State 1, Line 1
Cannot DROP TABLE 'cats' because it is being referenced by object 'udf_CatsByName_ITVF'.

Here’s what happens if I try to create the function without using two-part naming:

CREATE FUNCTION dbo.udf_CatsByName_ITVF( @CatName varchar(70) )
    RETURNS TABLE
    WITH SCHEMABINDING
AS
RETURN (
    SELECT 
        CatId,
        CatName,
        Phone
    FROM Cats
    WHERE CatName = @CatName
    );

GO

Result:

Msg 4512, Level 16, State 3, Procedure udf_CatsByName_ITVF, Line 7
Cannot schema bind table valued function 'dbo.udf_CatsByName_ITVF' because name 'Cats' is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.

Example 2 – Multi-Statement Table-Valued Function

With multi-statement TVFs, you place WITH SCHEMABINDING after the return variable specification.

CREATE FUNCTION dbo.udf_PetsByName_MSTVF( @PetName varchar(70))
    RETURNS @pets TABLE (
        PetId varchar(20),
        PetName varchar(70)
    )
    WITH SCHEMABINDING
AS
BEGIN
    INSERT INTO @pets
    SELECT 
        CONCAT('Cat', ' ', CatId),
        CatName
    FROM dbo.Cats
    WHERE CatName = @PetName;

    INSERT INTO @pets
    SELECT 
        CONCAT('Dog', ' ', DogId),
        DogName
    FROM dbo.Dogs
    WHERE DogName = @PetName;

    IF @@ROWCOUNT = 0
    BEGIN
        INSERT INTO @pets
        VALUES (
            '',
            'There are no pets of that name.'
            )
    END

    RETURN;
END;

GO

Example 3 – Scalar Function

Here’s a scalar function example:

CREATE FUNCTION dbo.ufn_CountAlbums (@ArtistId int)  
RETURNS smallint
WITH SCHEMABINDING
AS  
BEGIN
    DECLARE @AlbumCount int;
    SELECT @AlbumCount = COUNT(AlbumId)
    FROM dbo.Albums
    WHERE ArtistId = @ArtistId; 
    RETURN @AlbumCount;
END;

GO

Example 4 – Adding Multiple Arguments

You can specify multiple arguments as a comma-separated list. For example, if you want to specify schema binding and encryption, then you’ll need to add these as a comma separated list.

CREATE FUNCTION dbo.udf_CatsByName_ITVF( @CatName varchar(70) )
    RETURNS TABLE
    WITH SCHEMABINDING, ENCRYPTION
AS
RETURN (
    SELECT 
        CatId,
        CatName,
        Phone
    FROM dbo.Cats
    WHERE CatName = @CatName
    );

GO