- Error number
- 530
- Severity
- 16 (user-correctable error)
- State
- 1
- Product
- Microsoft SQL Server Database Engine
- Versions
- All supported SQL Server versions (2016 to 2025), Azure SQL Database, Azure SQL Managed Instance
- Category
- Query Processing & Performance
- Last reviewed
- 23 October 2026
On this page
What it means
Error 530 means a recursive common table expression (CTE) called itself more times than the recursion limit allows, so SQL Server stopped the statement.
A recursive CTE has an anchor query and a recursive member that refers back to the CTE. Each pass of the recursive member is one level of recursion. To protect the server from runaway queries, SQL Server stops after 100 levels by default and raises 530 at severity 16. The number in the message is the limit that was in force: 100 when the query has no hint, or the value of OPTION (MAXRECURSION n) when it has one.
The error has two very different causes, and the fix depends on which one you have. Either the data really is deeper than the limit (a calendar of 365 days, a number series, a deep bill of materials), and raising the limit for that query is correct. Or the data contains a loop, such as an employee who is their own manager two levels up, and the recursion would never end at any limit. Rows already streamed to the client before the error are not a complete result.
Common causes
- A number or date generator that needs more than 100 levels, for example one row per day for a year.
- A hierarchy (organisation chart, category tree, bill of materials) that is deeper than 100 levels or deeper than the MAXRECURSION value in the query.
- A cycle in parent and child data: A reports to B, B reports to C and C reports to A, so the recursive member never runs out of rows.
- A recursive member whose stop condition is wrong or missing, for example comparing the wrong column or never incrementing a counter.
Diagnose it
Read-only These queries only read metadata and dynamic management views. They do not change data, permissions or configuration, so they are safe to run first.
Measure how deep the data really goes (with a safety limit)
Carry a level counter and cap it, so the query finishes even if the data loops. Compare the result with the limit in the error message.
WITH chain AS (
SELECT id, manager_id, 0 AS lvl
FROM dbo.Employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.manager_id, c.lvl + 1
FROM dbo.Employees AS e
JOIN chain AS c ON e.manager_id = c.id
WHERE c.lvl < 1000
)
SELECT MAX(lvl) AS deepest_level, COUNT(*) AS rows_reached
FROM chain
OPTION (MAXRECURSION 1000); deepest_level is how many levels the data needs. If rows_reached is lower than the number of rows in the table, some rows are not connected to any root, which often means they sit in a loop.
Find loops in parent and child data
Walk up from every row, remember the path, and stop as soon as an id appears twice. Any row returned is part of a cycle.
WITH walk AS (
SELECT id AS start_id, manager_id,
CAST(CONCAT('/', id, '/') AS varchar(4000)) AS path, 0 AS is_cycle
FROM dbo.Employees
UNION ALL
SELECT w.start_id, e.manager_id,
CAST(CONCAT(w.path, e.id, '/') AS varchar(4000)),
CASE WHEN w.path LIKE CONCAT('%/', e.id, '/%') THEN 1 ELSE 0 END
FROM walk AS w
JOIN dbo.Employees AS e ON e.id = w.manager_id
WHERE w.is_cycle = 0
)
SELECT start_id, path
FROM walk
WHERE is_cycle = 1
OPTION (MAXRECURSION 1000); No rows when the hierarchy is clean. Each row returned shows a path such as /1/3/2/1/, which names the ids that form the loop.
Rows that point at themselves
SELECT id, name, manager_id
FROM dbo.Employees
WHERE manager_id = id; The simplest loop: a row that is its own parent. Any row here makes a recursive walk endless.
How to fix it
Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.
Raise the limit for this query when the depth is legitimate
Read-onlyAdd OPTION (MAXRECURSION n) to the statement that uses the CTE, with a value a little above the real depth. The hint goes at the end of the outer statement, not inside the CTE. The maximum is 32767.
WITH days AS (
SELECT CAST('2026-01-01' AS date) AS d
UNION ALL
SELECT DATEADD(day, 1, d) FROM days WHERE d < '2026-12-31'
)
SELECT COUNT(*) AS days_in_year
FROM days
OPTION (MAXRECURSION 366);
Generate number and date series without recursion
Read-onlyOn SQL Server 2022 and later (database compatibility level 160 or higher), GENERATE_SERIES returns a series of numbers with no recursion limit at all. Add the numbers to a start date with DATEADD to build a calendar.
SELECT DATEADD(day, value, CAST('2026-01-01' AS date)) AS d
FROM GENERATE_SERIES(0, 364);
Make the CTE stop on loops
Read-onlyWhen the data might contain a cycle, carry the path in the CTE and do not follow a row whose id is already in it, as in the diagnostic above. The query then returns every reachable row once and finishes, whatever the data looks like.
WITH chain AS (
SELECT id, manager_id, CAST(CONCAT('/', id, '/') AS varchar(4000)) AS path
FROM dbo.Employees
WHERE id = 1
UNION ALL
SELECT e.id, e.manager_id, CAST(CONCAT(c.path, e.id, '/') AS varchar(4000))
FROM dbo.Employees AS e
JOIN chain AS c ON e.id = c.manager_id
WHERE c.path NOT LIKE CONCAT('%/', e.id, '/%')
)
SELECT id, manager_id, path
FROM chain;
Break the loop in the data
Changes dataA cycle in a reporting line or category tree is almost always bad data. Agree with the data owner which link is wrong, correct it, and add a check so a row can no longer point at itself.
UPDATE dbo.Employees
SET manager_id = NULL -- Sara is the top of the chart
WHERE id = 1;
ALTER TABLE dbo.Employees
ADD CONSTRAINT CK_Employees_not_own_manager CHECK (manager_id <> id);
Remove the limit only for trusted, loop-free data
Read-onlyOPTION (MAXRECURSION 0) removes the limit. Use it only when the data cannot loop (for example, a constraint or the design guarantees it) and the depth can exceed 32767.
Caution: With MAXRECURSION 0 a loop in the data makes the query run until it is cancelled, using CPU and tempdb all the while. Prefer a specific limit.
WITH numbers AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 50000
)
SELECT MAX(n) AS highest
FROM numbers
OPTION (MAXRECURSION 0);
Examples
A number series longer than the default limit of 100
-- Generate the numbers 1 to 200 with a recursive CTE
WITH numbers AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 200
)
SELECT MAX(n) AS highest
FROM numbers;
Msg 530, Level 16, State 1, Line 2 The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Reproduced on SQL Server 2025 (17.0.1145.4), 23 October 2026.
One row per day for a year
WITH days AS (
SELECT CAST('2026-01-01' AS date) AS d
UNION ALL
SELECT DATEADD(day, 1, d) FROM days WHERE d < '2026-12-31'
)
SELECT COUNT(*) AS days_in_year
FROM days; -- needs 364 levels
Msg 530, Level 16, State 1, Line 1 The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Reproduced on SQL Server 2025 (17.0.1145.4), 23 October 2026.
A loop in the data: raising the limit does not help
SET NOCOUNT ON;
CREATE TABLE dbo.Employees (id INT PRIMARY KEY, name VARCHAR(50), manager_id INT);
-- Sara reports to Omar, Omar to Ali, Ali to Sara: a cycle
INSERT INTO dbo.Employees VALUES (1, 'Sara', 3), (2, 'Ali', 1), (3, 'Omar', 2);
WITH chain AS (
SELECT id, manager_id FROM dbo.Employees WHERE id = 1
UNION ALL
SELECT e.id, e.manager_id
FROM dbo.Employees AS e
JOIN chain AS c ON e.id = c.manager_id
)
SELECT COUNT(*) AS managers_above_sara
FROM chain
OPTION (MAXRECURSION 1000);
Msg 530, Level 16, State 1, Line 6 The statement terminated. The maximum recursion 1000 has been exhausted before statement completion.
Reproduced on SQL Server 2025 (17.0.1145.4), 23 October 2026.
How to prevent it
Decide the expected depth when you write a recursive CTE and put a matching MAXRECURSION hint on the statement, so a surprise in the data fails loudly at a known point instead of at the default of 100. For number and date series, prefer GENERATE_SERIES or a permanent numbers or calendar table over recursion.
Protect hierarchies at write time: a CHECK that a row is not its own parent, and application or trigger logic that refuses a new parent which is already a descendant. For practice with recursive queries, try the recursive employee hierarchy exercise.
Common questions
What is the default MAXRECURSION in SQL Server?
The default is 100 levels per statement. You can set any value from 0 to 32767 with OPTION (MAXRECURSION n) at the end of the statement that uses the CTE; 0 means no limit.
Can I put MAXRECURSION inside the CTE or a view?
No. It is a query hint, so it goes in the OPTION clause of the outer statement. A view cannot contain an OPTION clause; add the hint to the query that selects from the view.
Is it safe to just use MAXRECURSION 0?
Only when the data cannot loop. If there is a cycle, the query never finishes on its own. A specific limit just above the real depth is safer.
Learn more on CodeWithSQL
Official documentation
- WITH common_table_expression (Transact-SQL)
- Query hints: MAXRECURSION (Transact-SQL)
- Database Engine events and errors 0 to 999
Last technically reviewed 23 October 2026; examples and diagnostics run on SQL Server 2025 (17.0.1145.4). Message text checked against the engine's sys.messages catalogue.