Stored Procedures vs Functions in SQL Server: When to Choose Which

Written by

in

Stored Procedures vs Functions in SQL Server: When to Choose Which

These get confused constantly, including in interviews. Here’s the clean distinction, grounded in everything you’ve built across Chapters 2 and 4 rather than as a fresh set of rules to memorize.

Procedure vs Function(the one rule, sketched out)Need to WRITE data, orcontrol a TRANSACTION?(or return multiple result sets?)YESNOPROCEDURE✓ INSERT / UPDATE / DELETE✓ BEGIN/COMMIT/ROLLBACK, TRY/CATCH✓ can return many result sets✗ cannot be called inside SELECTFUNCTION✓ callable inside a SELECT / JOIN✓ composable, read-only building block✗ cannot write data (compile error)✗ no BEGIN/COMMIT or TRY/CATCH

Side by Side

Stored Procedure Function (any type)
Modify data (INSERT/UPDATE/DELETE) Yes No — compile error if attempted
Manage transactions (BEGIN/COMMIT/ROLLBACK) Yes No
Callable inside a SELECT statement No Yes
Return multiple result sets Yes No — exactly one value or one table
Use TRY/CATCH Yes No
Precompiled and cached like a procedure Yes Scalar/mTVF: yes; iTVF: inlines instead (Chapter 2)

The One-Sentence Rule

Need to write data, manage a transaction, or return multiple result sets? → Procedure. Otherwise → Function.

Functions’ composability inside SELECT statements is their key advantage — but only for read-only, single-result-shape logic. The moment you need to write data or control a transaction explicitly, you’re in stored procedure territory, no exceptions. This isn’t a style preference; it’s enforced by the engine, as Chapter 2’s “side-effecting operator” error demonstrated directly.

A Realistic Mixed Scenario

Real applications typically need both, working together: an iTVF to expose a reusable, JOIN-friendly “active customers this quarter” query, and a stored procedure that uses that same iTVF internally as part of a larger workflow that also writes an audit log row and sends the result back to the caller.

CREATE PROCEDURE dbo.usp_GenerateQuarterlyReport @quarter INT AS
BEGIN
    SET NOCOUNT ON;
    -- Reuses an iTVF from earlier in the chapter for the read-only part
    SELECT * FROM dbo.GetEmployeesByDepartment('Sales');
    -- Then does something only a procedure can: write an audit trail
    INSERT INTO dbo.ReportLog (report_name, generated_at) VALUES ('Quarterly Sales', SYSDATETIME());
END;

This is the natural end state once you’ve internalized both tools: functions for the composable, read-only building blocks; procedures for orchestrating them alongside anything with a side effect.

Practice tip: Look back at any function you wrote in Chapter 2 and ask: “if I needed this to also log who called it, could I?” The answer is no — that’s the exact moment a function needs to become, or be wrapped by, a procedure instead.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.