Reputation: 2275
I have a hierarchical structure in a SQL Server database. I'm trying to write a query to get all elements in the structure under a given element.
So, given a DB table with the columns id and parent_id, this is what I do:
WITH recursive_cte (root_id, id) AS (
SELECT parent_id, id
FROM test_cte
UNION ALL
SELECT t.parent_id, r.id
FROM test_cte t
INNER JOIN recursive_cte r
ON (r.root_id=t.id)
)
SELECT *
FROM recursive_cte
WHERE root_id=0
Now, if there is a circular reference in the structure under the element with id=0 I get an error from the DBMS (The maximum recursion 100 has been exhausted before statement completion). This is fine, the existance of circular references is already an error.
But if I have a circular reference in the structure under another element, the query will always give an error. Even if I specify a condition which restricts the record set to a non circular one (e.g WHERE root_id=0
).
For example in:
id|parent_id|name |
--+---------+---------------+
0 |NULL |TEST A |
1 |4 |TEST CIRCULAR |
2 |0 |TEST B |
3 |2 |TEST C |
4 |1 |TEST CIRCULAR B|
I want my query to work without errors with the condition root_id=0
. Is there a way to do that?
Upvotes: 3
Views: 5622
Reputation: 1269933
This may not be an elegant solution, but it seems to work. Store the list of visited ids in a list and exclude them from further searches. I think this is the right comparison for your query:
WITH recursive_cte(root_id, id, ids) AS (
SELECT parent_id, id, ',' + cast(id as varchar(8000)) + ',' as ids
FROM test_cte
UNION ALL
SELECT t.parent_id, r.id, ids + cast(id as varchar(8000)) + ','
FROM test_cte t INNER JOIN
recursive_cte r
ON r.root_id = t.id
WHERE ',' + r.ids + ',' not like '%,' + cast(t.id as varchar(8000)) + ',%'
)
SELECT *
FROM recursive_cte
WHERE root_id = 0;
Upvotes: 5
Reputation: 10680
You will need to put your WHERE-filter in the CTE part of your query, like so:
WITH recursive_cte (root_id, id) AS (
SELECT parent_id, id
FROM test_cte
WHERE id=0 -- Restrict your recursion to start from the item with id = 0, instead of considdering all items.
UNION ALL
SELECT t.parent_id, r.id
FROM test_cte t
INNER JOIN recursive_cte r
ON (r.root_id=t.id)
)
SELECT *
FROM recursive_cte
Upvotes: 0