J'ai le schéma suivant (les noms ont changé), que je ne peux pas changer:
CREATE TABLE MyTable (
Id INT NOT NULL PRIMARY KEY,
ParentId INT NOT NULL
);
ALTER TABLE MyTable ADD FOREIGN KEY (ParentId) REFERENCES MyTable(Id);
Autrement dit, chaque enregistrement est un enfant d'un autre enregistrement. Si un enregistrement ParentId
est égal à son Id
, alors l'enregistrement est considéré comme un nœud racine.
Je veux exécuter une requête qui trouvera toutes les références circulaires. Par exemple, avec les données
INSERT INTO MyTable (Id, ParentId) VALUES
(0, 0),
(1, 0),
(2, 4),
(3, 2),
(4, 3);
la requête doit retourner
Id | Cycle
2 | 2 < 4 < 3 < 2
3 | 3 < 2 < 4 < 3
4 | 4 < 3 < 2 < 4
J'ai écrit la requête suivante pour SQL Server 2008 R2, et je me demande si cette requête peut être améliorée:
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
la source
la source
0 > 0
ne devrait pas être considéré comme un cycle?ParentId
est égal à sonId
, ce n'est donc pas un cycle pour ce scénario.Réponses:
Cela nécessite un CTE récursif:
Voyez-le en action ici: SQL Fiddle
Mise à jour:
Ajout d'une distance pour pouvoir exclure tous les auto-cycles (voir le commentaire de ypercube):
SQL Fiddle
Lequel vous devez utiliser dépend de vos besoins.
la source
6 > 6
, tant qu'il ne l'est pas0 > 0
.WHERE Id <> ParentId
la première partie du CTE.AND C.ParentId <> C.Id
n'est pas assez.A->B, B->C, C->B
Si un chemin mène à un cercle plus long ( ), vous obtiendriez toujours une récursion infinie pour construire les chemins commençant parA
. Vous auriez vraiment besoin de vérifier le chemin complet.la source