Stored Procedures > Recursive Stored Procedures
Recursive stored procedures in SQL Server are procedures that call themselves. This technique is particularly useful for querying hierarchical data structures, such as organizational charts, bill of materials, or file system directories. By repeatedly executing the same logic, you can traverse deeply nested data relationships.
A recursive procedure needs two main components:
Consider a simple employee hierarchy where each employee reports to a manager. We can use a recursive stored procedure to find all subordinates of a given employee, or to list the entire reporting chain upwards.
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY,
EmployeeName VARCHAR(100),
ManagerID INT NULL
);
INSERT INTO Employees (EmployeeID, EmployeeName, ManagerID) VALUES
(1, 'CEO', NULL),
(2, 'VP Sales', 1),
(3, 'VP Engineering', 1),
(4, 'Sales Manager', 2),
(5, 'Engineer 1', 3),
(6, 'Engineer 2', 3),
(7, 'Sales Rep 1', 4),
(8, 'Sales Rep 2', 4);
This procedure finds all direct and indirect subordinates of a given employee.
CREATE PROCEDURE GetSubordinates
@EmployeeID INT
AS
BEGIN
SET NOCOUNT ON;
-- Anchor Member: Select the current employee
SELECT
E.EmployeeID,
E.EmployeeName,
0 AS Level
FROM Employees AS E
WHERE E.EmployeeID = @EmployeeID
UNION ALL
-- Recursive Member: Select subordinates of the current employee
SELECT
E.EmployeeID,
E.EmployeeName,
Subordinates.Level + 1
FROM Employees AS E
INNER JOIN GetSubordinates AS Subordinates
ON E.ManagerID = Subordinates.EmployeeID
WHERE Subordinates.Level < MAX_RECURSION_DEPTH; -- Optional: Limit recursion depth
END;
Important: The sample above uses a common pattern for recursion, but in modern SQL Server versions (2005+), the preferred and more efficient way to handle hierarchical data is using Common Table Expressions (CTEs) with recursion.
CTEs provide a cleaner and more structured approach to recursion.
This CTE-based procedure lists all employees and their respective levels in the hierarchy, starting from a specified employee.
CREATE PROCEDURE GetEmployeeHierarchy
@EmployeeID INT
AS
BEGIN
SET NOCOUNT ON;
WITH EmployeeHierarchy (EmployeeID, EmployeeName, ManagerID, Level)
AS
(
-- Anchor Member: The starting employee
SELECT
E.EmployeeID,
E.EmployeeName,
E.ManagerID,
0 AS Level
FROM Employees AS E
WHERE E.EmployeeID = @EmployeeID
UNION ALL
-- Recursive Member: Fetch direct reports
SELECT
E.EmployeeID,
E.EmployeeName,
E.ManagerID,
EH.Level + 1
FROM Employees AS E
INNER JOIN EmployeeHierarchy AS EH
ON E.ManagerID = EH.EmployeeID
WHERE EH.Level < MAX_RECURSION_DEPTH -- Optional: Limit depth
)
SELECT
EmployeeID,
EmployeeName,
Level
FROM EmployeeHierarchy
ORDER BY Level, EmployeeName;
END;
EXEC GetEmployeeHierarchy @EmployeeID = 1;
OPTION (MAXRECURSION n)
clause, but be cautious as very high values can consume excessive memory. Use OPTION (MAXRECURSION 0)
to disable the limit, but this is generally not recommended for production environments without careful analysis.For very large or complex hierarchies, consider alternative data structures like Adjacency Lists, Nested Sets, or Materialized Paths, which might offer better performance for certain operations.