Tengo el siguiente esquema (nombres cambiados), que no puedo cambiar:
CREATE TABLE MyTable (
Id INT NOT NULL PRIMARY KEY,
ParentId INT NOT NULL
);
ALTER TABLE MyTable ADD FOREIGN KEY (ParentId) REFERENCES MyTable(Id);
Es decir, cada registro es hijo de otro registro. Si un registro ParentId
es igual a su Id
, entonces el registro se considera un nodo raíz.
Quiero ejecutar una consulta que encontrará todas las referencias circulares. Por ejemplo, con los datos
INSERT INTO MyTable (Id, ParentId) VALUES
(0, 0),
(1, 0),
(2, 4),
(3, 2),
(4, 3);
la consulta debe devolver
Id | Cycle
2 | 2 < 4 < 3 < 2
3 | 3 < 2 < 4 < 3
4 | 4 < 3 < 2 < 4
Escribí la siguiente consulta para SQL Server 2008 R2, y me pregunto si esta consulta se puede mejorar:
IF OBJECT_ID(N'tempdb..#Results') IS NOT NULL DROP TABLE #Results;
CREATE TABLE #Results (Id INT, HasParentalCycle BIT, Cycle VARCHAR(MAX));
DECLARE @i INT,
@j INT,
@flag BIT,
@isRoot BIT,
@ids VARCHAR(MAX);
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Id
FROM MyTable;
OPEN MyCursor;
FETCH NEXT FROM MyCursor INTO @i;
WHILE @@FETCH_STATUS = 0
BEGIN
IF OBJECT_ID(N'tempdb..#Parents') IS NOT NULL DROP TABLE #Parents;
CREATE TABLE #Parents (Id INT);
SET @ids = NULL;
SET @isRoot = 0;
SET @flag = 0;
SET @j = @i;
INSERT INTO #Parents (Id) VALUES (@j);
WHILE (1=1)
BEGIN
SELECT
@j = ParentId,
@isRoot = CASE WHEN ParentId = Id THEN 1 ELSE 0 END
FROM MyTable
WHERE Id = @j;
IF (@isRoot = 1)
BEGIN
SET @flag = 0;
BREAK;
END
IF EXISTS (SELECT 1 FROM #Parents WHERE Id = @j)
BEGIN
INSERT INTO #Parents (Id) VALUES (@j);
SET @flag = 1;
SELECT @ids = COALESCE(@ids + ' < ', '') + CAST(Id AS VARCHAR) FROM #Parents;
BREAK;
END
ELSE
BEGIN
INSERT INTO #Parents (Id) VALUES (@j);
END
END
INSERT INTO #Results (Id, HasParentalCycle, Cycle) VALUES (@i, @flag, @ids);
FETCH NEXT FROM MyCursor INTO @i;
END
CLOSE MyCursor;
DEALLOCATE MyCursor;
SELECT Id, Cycle
FROM #Results
WHERE HasParentalCycle = 1;
sql-server
sql-server-2008-r2
foreign-key
cubetwo1729
fuente
fuente
0 > 0
no debe considerarse un ciclo?ParentId
igual a suId
, por lo que no es un ciclo para este escenario.Respuestas:
Esto requiere un CTE recursivo:
Véalo en acción aquí: SQL Fiddle
Actualizar:
Distancia añadida para poder excluir todos los ciclos personales (ver el comentario de ypercube):
Violín de SQL
Cuál debe usar depende de sus requisitos.
fuente
6 > 6
, siempre que no lo sea0 > 0
.WHERE Id <> ParentId
la primera parte del CTE.AND C.ParentId <> C.Id
no es suficiente. Si un camino conduce a un círculo más largo (A->B, B->C, C->B
), aún obtendría una recursión infinita para construir los caminos desde el principioA
. Realmente necesitaría verificar el camino completo.fuente