Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

sql - Recursive CTE in presence of circular references

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...