User-Defined Functions
Learn the difference between scalar and table-valued functions in SQL Server, how to create them with CREATE FUNCTION, and how to call them in a query.
Introduction
A user-defined function (UDF) packages a calculation or a reusable result set behind a name that you can call directly inside a query, the same way you would call a built-in function like UPPER() or GETDATE(). SQL Server supports two broad kinds: scalar functions, which return a single value, and table-valued functions, which return a result set you can query like a table. This lesson covers both, with runnable examples of each.
- The difference between scalar and table-valued functions.
- How to create and call a scalar function.
- How to create and query an inline table-valued function.
- How multi-statement table-valued functions differ.
- When to reach for a function instead of a procedure.
Scalar vs Table-Valued
| Type | Returns | Used Where |
|---|---|---|
| Scalar function | A single value (INT, VARCHAR, DATE, etc.) | In a SELECT list, WHERE clause, or anywhere an expression is valid |
| Inline table-valued function (iTVF) | A table, defined by a single SELECT | In a FROM clause, like a parameterized view |
| Multi-statement table-valued function (MSTVF) | A table, built up with multiple statements | In a FROM clause, for logic too complex for one SELECT |
Creating a Scalar Function
A scalar function is defined with CREATE FUNCTION, a RETURNS clause naming the data type it produces, and a body that computes and returns that value.
CREATE FUNCTION dbo.fn_GetFullName( @FirstName VARCHAR(50), @LastName VARCHAR(50))RETURNS VARCHAR(101)ASBEGIN RETURN @FirstName + ' ' + @LastName;END;GOCalling a Scalar Function
A scalar function must be called with its schema prefix (dbo.) and can be used anywhere an expression is allowed, including a SELECT list.
SELECT CustomerID, dbo.fn_GetFullName(FirstName, LastName) AS FullNameFROM dbo.CustomersWHERE City = 'Seattle';Click Run to see what this code prints.
Scalar functions that query tables internally are historically a common performance trap in SQL Server, because (on older compatibility levels) they run row-by-row instead of set-based. Recent versions include 'scalar UDF inlining' that can help, but it is still worth preferring simple, computation-only scalar functions and using table-valued functions or views for anything that touches other tables.
Inline Table-Valued Functions
An inline table-valued function behaves like a parameterized view: its body is a single RETURN (SELECT ...) statement, and SQL Server expands it into the calling query, which usually makes it fast.
CREATE FUNCTION dbo.fn_GetOrdersByCustomer( @CustomerID INT)RETURNS TABLEASRETURN( SELECT OrderID, OrderDate, Status FROM dbo.Orders WHERE CustomerID = @CustomerID);GO
SELECT * FROM dbo.fn_GetOrdersByCustomer(104)ORDER BY OrderDate DESC;Click Run to see what this code prints.
Multi-Statement Table-Valued Functions
When the logic needs more than one statement - for example, populating a table variable step by step - use a multi-statement table-valued function. It explicitly declares the shape of the returned table with RETURNS @TableVar TABLE (...).
CREATE FUNCTION dbo.fn_GetTopCustomers( @MinOrders INT)RETURNS @Result TABLE( CustomerID INT, OrderCount INT)ASBEGIN INSERT INTO @Result (CustomerID, OrderCount) SELECT CustomerID, COUNT(*) FROM dbo.Orders GROUP BY CustomerID HAVING COUNT(*) >= @MinOrders;
RETURN;END;GO
SELECT * FROM dbo.fn_GetTopCustomers(3);Click Run to see what this code prints.
Functions vs Procedures
Functions and procedures overlap in purpose but are not interchangeable. A function can be used inside a larger SELECT statement, but it cannot modify data or use TRY/CATCH, and it always has to return something. A procedure can perform inserts, updates, and deletes, use transactions and error handling, but cannot be called directly inside a SELECT list or FROM clause the way a function can.
Common Mistakes
- Writing a scalar function that queries a large table and calling it once per row in a SELECT - this can be very slow.
- Forgetting the dbo. schema prefix when calling a function.
- Trying to use a function to INSERT/UPDATE/DELETE data - functions cannot modify data outside table variables.
- Choosing a multi-statement table-valued function when a simpler inline table-valued function would do, losing optimizer flexibility.
- Not indexing the columns a table-valued function filters on, just as you would for any other query.
Best Practices
- Prefer inline table-valued functions over multi-statement ones when the logic fits in a single SELECT.
- Keep scalar functions to pure computation where possible - avoid querying tables inside them if you can use a join or an iTVF instead.
- Name functions with a clear fn_ prefix so they are easy to distinguish from tables and views.
- Test functions with a range of inputs, including NULLs and boundary values.
- Document the expected shape of the returned table for table-valued functions.
Frequently Asked Questions
No, functions cannot execute stored procedures or run dynamic SQL - they are restricted to read-only, deterministic-friendly operations so SQL Server can safely use them inside larger queries.
Unlike built-in functions, user-defined scalar and table-valued functions always require an explicit schema prefix when called - it is a SQL Server parsing rule, not a style choice.
Inline table-valued functions perform similarly to views since both get expanded into the outer query, but functions have the added benefit of accepting parameters, which plain views cannot.
No, functions can only return their declared return value; if you need OUTPUT parameters, use a stored procedure instead.
Key Takeaways
- Scalar functions return one value; table-valued functions return a table.
- Inline table-valued functions act like parameterized views and are usually the most efficient option.
- Multi-statement table-valued functions support more complex logic at some performance cost.
- Functions cannot modify data outside of table variables and cannot call procedures.
- Always call user-defined functions with their schema prefix, such as dbo.fn_GetFullName.
Summary
User-defined functions let you package reusable calculations and parameterized result sets directly into your T-SQL vocabulary. Next, you will look at triggers - code that runs automatically in response to data changes, rather than being called explicitly.