Stored procedures and parameters
Save T-SQL as a procedure and call it with EXEC: input parameters, defaults, named and positional calls, OUTPUT parameters and the forgotten-OUTPUT trap, return codes, checking input with THROW, and the missing-GO trap.
CREATE OR ALTER PROCEDURE dbo.GetProductsByCategory @CategoryId intASBEGIN SET NOCOUNT ON; SELECT ProductId, Name, Price, Stock FROM dbo.Products WHERE CategoryId = @CategoryId ORDER BY Price DESC;END;GOEXEC dbo.GetProductsByCategory @CategoryId = 1;ProductId Name Price Stock
--------- ---------------- ----- -----
2 Cordless drill 89.00 12
1 Claw hammer 18.50 40
3 Tape measure 8 m 9.75 65SET NOCOUNT ON stops the “(3 rows affected)” message after each statement. Those messages are extra network traffic and can confuse some client libraries, so it is the usual first line of a procedure.
Calling with parameters
EXEC dbo.GetProductsByCategory 4; -- by positionEXEC dbo.GetProductsByCategory @CategoryId = 6; -- by nameEXEC dbo.GetProductsByCategory; -- missingProductId Name Price Stock
--------- ----------------- ----- -----
8 Wood screws (200) 11.90 150
9 Wall plugs (100) 4.50 0
ProductId Name Price Stock
--------- -------------- ----- -----
12 Safety goggles 6.80 0
Msg 201, Level 16, State 4, Procedure dbo.GetProductsByCategory, Line 0
Procedure or function 'GetProductsByCategory' expects parameter '@CategoryId', which was not supplied.Defaults
CREATE OR ALTER PROCEDURE dbo.GetProductsByCategory @CategoryId int, @MaxPrice decimal(10,2) = NULL -- optionalASBEGIN SET NOCOUNT ON; SELECT ProductId, Name, Price, Stock FROM dbo.Products WHERE CategoryId = @CategoryId AND (@MaxPrice IS NULL OR Price <= @MaxPrice) ORDER BY Price DESC;END;GOEXEC dbo.GetProductsByCategory @CategoryId = 1;EXEC dbo.GetProductsByCategory @CategoryId = 1, @MaxPrice = 20;ProductId Name Price Stock
--------- ---------------- ----- -----
2 Cordless drill 89.00 12
1 Claw hammer 18.50 40
3 Tape measure 8 m 9.75 65
ProductId Name Price Stock
--------- ---------------- ----- -----
1 Claw hammer 18.50 40
3 Tape measure 8 m 9.75 65OUTPUT parameters
OUTPUT. This one adds stock and hands back the new level. The caller must say OUTPUT too:
CREATE OR ALTER PROCEDURE dbo.AddStock @ProductId int, @Qty int = 1, @NewStock int OUTPUTASBEGIN SET NOCOUNT ON; UPDATE dbo.Products SET Stock = Stock + @Qty WHERE ProductId = @ProductId; SELECT @NewStock = Stock FROM dbo.Products WHERE ProductId = @ProductId;END;GODECLARE @Stock int; EXEC dbo.AddStock @ProductId = 9, @Qty = 50, @NewStock = @Stock OUTPUT;SELECT @Stock AS WithOutput; EXEC dbo.AddStock @ProductId = 9, @Qty = 50, @NewStock = @Stock;SELECT @Stock AS WithoutOutput, (SELECT Stock FROM dbo.Products WHERE ProductId = 9) AS RealStock;WithOutput
----------
50
WithoutOutput RealStock
------------- ---------
50 100Return codes and checking input
RETURN n ends a procedure and hands back an integer status. By convention 0 means success. For a real error, raise one with THROW: it stops the procedure and the caller sees the message (the Error handling lesson covers THROW in full).
CREATE OR ALTER PROCEDURE dbo.AddStock @ProductId int, @Qty int = 1, @NewStock int OUTPUTASBEGIN SET NOCOUNT ON; IF @Qty <= 0 THROW 50001, N'Quantity must be at least 1.', 1; IF NOT EXISTS (SELECT 1 FROM dbo.Products WHERE ProductId = @ProductId) RETURN 1; -- no such product UPDATE dbo.Products SET Stock = Stock + @Qty WHERE ProductId = @ProductId; SELECT @NewStock = Stock FROM dbo.Products WHERE ProductId = @ProductId; RETURN 0;END;GODECLARE @rc int, @Stock int; EXEC @rc = dbo.AddStock @ProductId = 99, @Qty = 5, @NewStock = @Stock OUTPUT;SELECT @rc AS ReturnCode, @Stock AS NewStock; EXEC @rc = dbo.AddStock @ProductId = 12, @NewStock = @Stock OUTPUT; -- @Qty defaults to 1SELECT @rc AS ReturnCode, @Stock AS NewStock; EXEC @rc = dbo.AddStock 12, -5, @Stock OUTPUT;ReturnCode NewStock
---------- --------
1 NULL
ReturnCode NewStock
---------- --------
0 1
Msg 50001, Level 16, State 1, Procedure dbo.AddStock, Line 10
Quantity must be at least 1.DEFAULT in its place: EXEC dbo.AddStock 12, DEFAULT, @Stock OUTPUT;.
Saving results from a procedure
INSERT … EXEC. The table must have matching columns:
CREATE TABLE #Tools (ProductId int, Name nvarchar(60), Price decimal(10,2), Stock int); INSERT INTO #ToolsEXEC dbo.GetProductsByCategory @CategoryId = 1; SELECT COUNT(*) AS Tools, SUM(Price * Stock) AS StockValue FROM #Tools;DROP TABLE #Tools;Tools StockValue
----- ----------
3 2441.75The missing GO trap
CREATE OR ALTER PROCEDURE dbo.CountOrdersAS SELECT COUNT(*) AS Orders FROM dbo.Orders; SELECT TOP (2) OrderId, Status FROM dbo.Orders ORDER BY OrderId; -- meant as a testGOEXEC dbo.CountOrders;Orders
------
10
OrderId Status
------- -------
1 Shipped
2 ShippedEXEC dbo.CountOrders;, the procedure would call itself until SQL Server stopped it at 32 levels deep (Msg 217). Always put GO straight after a procedure.
Show a hint
Show the solution
CREATE OR ALTER PROCEDURE dbo.CustomerOrders @CustomerId int, @Status varchar(10) = NULLASBEGIN SET NOCOUNT ON; SELECT OrderId, OrderDate, Status FROM dbo.Orders WHERE CustomerId = @CustomerId AND (@Status IS NULL OR Status = @Status) ORDER BY OrderDate;END;GOEXEC dbo.CustomerOrders @CustomerId = 4;EXEC dbo.CustomerOrders @CustomerId = 2, @Status = 'Pending';OrderId OrderDate Status
------- ---------- -------
4 2024-03-15 Shipped
7 2024-05-05 Shipped
OrderId OrderDate Status
------- ---------- -------
9 2024-06-01 PendingRemember
A stored procedure is named, saved T-SQL; call it with EXEC and parameters. Start procedures with SET NOCOUNT ON; use CREATE OR ALTER so scripts can be rerun. Pass parameters by name for clarity; a parameter without a default must be supplied (Msg 201). A NULL default plus (@p IS NULL OR col = @p) makes a filter optional. OUTPUT is needed in both the definition and the call; forget it at the call and the value is silently lost. RETURN gives an integer status (0 = success); THROW raises a real error. INSERT … EXEC captures a procedure’s result set into a table. A procedure runs to the end of its batch: always put GO after it.