Scalar and table-valued functions
Your own functions: scalar functions that return one value, inline table-valued functions that work like views with parameters, multi-statement table-valued functions, what functions are not allowed to do, and why inline functions are usually the fast choice.
| Kind | Returns | Used in |
|---|---|---|
Scalar functions
CREATE OR ALTER FUNCTION dbo.PriceWithVat (@Price decimal(10,2))RETURNS decimal(10,2)ASBEGIN RETURN ROUND(@Price * 1.20, 2);END;GOSELECT Name, Price, dbo.PriceWithVat(Price) AS WithVatFROM dbo.ProductsWHERE dbo.PriceWithVat(Price) > 50ORDER BY Price DESC;Name Price WithVat ------------------- ----- ------- Cordless drill 89.00 106.80 Masonry paint 10 L 54.00 64.80 Plywood sheet 18 mm 42.00 50.40
SELECT Name, PriceWithVat(Price) AS WithVat FROM dbo.Products;Msg 195, Level 15, State 10, Line 1 'PriceWithVat' is not a recognized built-in function name.
Inline table-valued functions
CREATE OR ALTER FUNCTION dbo.OrdersFor (@CustomerId int)RETURNS TABLEASRETURN SELECT o.OrderId, o.OrderDate, o.Status, SUM(i.Quantity * i.UnitPrice) AS Total FROM dbo.Orders AS o JOIN dbo.OrderItems AS i ON i.OrderId = o.OrderId WHERE o.CustomerId = @CustomerId GROUP BY o.OrderId, o.OrderDate, o.Status;GOSELECT * FROM dbo.OrdersFor(4) ORDER BY OrderDate;OrderId OrderDate Status Total
------- ---------- ------- ------
4 2024-03-15 Shipped 524.00
7 2024-05-05 Shipped 199.50CROSS APPLY it runs once per row of another table, which you met in the APPLY lesson:
SELECT c.FirstName, f.OrderId, f.TotalFROM dbo.Customers AS cCROSS APPLY dbo.OrdersFor(c.CustomerId) AS fWHERE f.Status = 'Shipped' AND f.Total > 100ORDER BY f.Total DESC;FirstName OrderId Total --------- ------- ------ Dev 4 524.00 Dev 7 199.50 Farid 5 120.50 Ben 2 113.00 Asha 3 112.80
Multi-statement table-valued functions
CREATE OR ALTER FUNCTION dbo.StockReport (@Below int)RETURNS @Report TABLE (ProductId int, Name nvarchar(60), Stock int, Advice nvarchar(20))ASBEGIN INSERT INTO @Report (ProductId, Name, Stock) SELECT ProductId, Name, Stock FROM dbo.Products WHERE Stock < @Below; UPDATE @Report SET Advice = N'Reorder now' WHERE Stock = 0; UPDATE @Report SET Advice = N'Reorder soon' WHERE Advice IS NULL; RETURN;END;GOSELECT * FROM dbo.StockReport(30) ORDER BY Stock;ProductId Name Stock Advice
--------- ------------------ ----- ------------
9 Wall plugs (100) 0 Reorder now
12 Safety goggles 0 Reorder now
2 Cordless drill 12 Reorder soon
10 Masonry paint 10 L 25 Reorder soonWhat functions cannot do
CREATE OR ALTER FUNCTION dbo.TakeStock (@ProductId int)RETURNS intASBEGIN UPDATE dbo.Products SET Stock = Stock - 1 WHERE ProductId = @ProductId; RETURN 1;END;Msg 443, Level 16, State 15, Procedure TakeStock, Line 5 Invalid use of a side-effecting operator 'UPDATE' within a function.
Scalar functions and performance
sys.sql_modules tells you which ones can:
CREATE OR ALTER FUNCTION dbo.DaysSince (@d date)RETURNS intASBEGIN RETURN DATEDIFF(day, @d, GETDATE());END;GOSELECT OBJECT_NAME(object_id) AS FunctionName, is_inlineableFROM sys.sql_modulesWHERE OBJECT_NAME(object_id) IN (N'PriceWithVat', N'DaysSince')ORDER BY FunctionName;FunctionName is_inlineable ------------ ------------- DaysSince 0 PriceWithVat 1
| Stored procedure | Function | |
|---|---|---|
EXEC | ||
Show a hint
Show the solution
CREATE OR ALTER FUNCTION dbo.ProductsInCategory (@CategoryName nvarchar(40))RETURNS TABLEASRETURN SELECT p.Name, p.Price FROM dbo.Products AS p JOIN dbo.Categories AS c ON c.CategoryId = p.CategoryId WHERE c.Name = @CategoryName;GOSELECT Name, PriceFROM dbo.ProductsInCategory(N'Timber')ORDER BY Price;Name Price ------------------- ----- Timber 2x4 2.4 m 6.80 Plywood sheet 18 mm 42.00
Remember
Scalar functions return one value; table-valued functions return a table and go in FROM or APPLY. Call scalar UDFs with their schema (dbo.Fn), or SQL Server looks for a built-in (Msg 195). An inline TVF is one SELECT with parameters, a view with parameters, and is expanded into the query for free. A multi-statement TVF fills a table variable step by step; the optimizer cannot see inside it. Functions cannot change data, call procedures that do, or use TRY … CATCH (Msg 443). SQL Server 2019+ inlines simple scalar functions; sys.sql_modules.is_inlineable shows which. When in doubt, write an inline table-valued function.