SQL Server: Comment limiter la récursivité CTE aux lignes simplement ajoutées de manière récursive?
Exemple Plus Simple
Essayons un exemple plus simple, afin que les gens peuvent envelopper leurs têtes autour des concepts, et d'avoir un exemple concret que vous pouvez copier&collez-le dans la Requête SQL Analizer:
Imaginer un Nœuds table, avec une hiérarchie:
A
- B
- C
Nous pouvons commencer à faire des tests dans la Requête Analizer:
CREATE TABLE ##Nodes
(
NodeID varchar(50) PRIMARY KEY NOT NULL,
ParentNodeID varchar(50) NULL
)
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('A', null)
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('B', 'A')
INSERT INTO ##Nodes (NodeID, ParentNodeID) VALUES ('C', 'B')
De sortie souhaité:
ParentNodeID NodeID GenerationsRemoved
============ ====== ==================
NULL A 1
NULL B 2
NULL C 3
A B 1
A C 2
B C 1
Maintenant le suggère CTE expression, il est incorrect de sortie:
WITH NodeChildren AS
(
--initialization
SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
FROM ##Nodes
WHERE ParentNodeID IS NULL
UNION ALL
--recursive execution
SELECT P.ParentNodeID, N.NodeID, P.GenerationsRemoved + 1
FROM NodeChildren AS P
INNER JOIN ##Nodes AS N
ON P.NodeID = N.ParentNodeID
)
SELECT ParentNodeID, NodeID, GenerationsRemoved
FROM NodeChildren
De sortie réelle:
ParentNodeID NodeID GenerationsRemoved
============ ====== ==================
NULL A 1
NULL B 2
NULL C 3
Remarque: Si SQL Server 2005† CTE ne peut pas faire ce que je faisais avant en 2000‡, c'est très bien, et c'est la réponse. Et celui qui donne "il n'est pas possible" que la réponse va gagner le butin. Mais je vais attendre quelques jours pour que tout le monde d'accord est qu'il n'est pas possible avant que je irrecovably donner 250 points de réputation pour une non-solution à mon problème.
Nitpickers Coin
† † 2008
‡sans avoir recours à un UDF*, qui est la solution déjà
*sauf si vous pouvez voir un moyen d'améliorer la performance de l'UDF dans la question d'origine
Question D'Origine
j'ai un tableau de Nœuds, chacun avec un parent qui pointe vers un autre Nœud (ou null).
À des fins d'illustration:
1 My Computer
2 Drive C
4 Users
5 Program Files
7 Windows
8 System32
3 Drive D
6 mp3
je veux une table qui renvoie toutes les relations parent-enfant, et le nombre de générations entre elles
Pour pour tout parent direct de la relation:
ParentNodeID ChildNodeID GenerationsRemoved
============ =========== ===================
(null) 1 1
1 2 1
2 4 1
2 5 1
2 7 1
1 3 1
3 6 1
7 8 1
Mais ensuite il y a les grands-parents de relations:
ParentNodeID ChildNodeID GenerationsRemoved
============ =========== ===================
(null) 2 2
(null) 3 2
1 4 2
1 5 2
1 7 2
1 6 2
2 8 2
Et la il y a la grand-grand-grands-parents de relations:
ParentNodeID ChildNodeID GenerationsRemoved
============ =========== ===================
(null) 4 3
(null) 5 3
(null) 7 3
(null) 6 3
1 8 3
Donc je peux comprendre la base de la CTE d'initialisation:
WITH (NodeChildren) AS
{
--initialization
SELECT ParentNodeID, NodeID AS ChildNodeID, 1 AS GenerationsRemoved
FROM Nodes
}
Le problème est désormais de la partie récursive. La réponse la plus évidente, bien sûr, ne fonctionne pas:
WITH (NodeChildren) AS
{
--initialization
SELECT ParentNodeID, ChildNodeID, 1 AS GenerationsRemoved
FROM Nodes
UNION ALL
--recursive execution
SELECT parents.ParentNodeID, children.NodeID, parents.Generations+1
FROM NodeChildren parents
INNER JOIN NodeParents children
ON parents.NodeID = children.ParentNodeID
}
Msg 253, Level 16, State 1, Line 1
Recursive member of a common table expression 'NodeChildren' has multiple recursive references.
Toutes les informations nécessaires pour générer l'ensemble de la liste récursive est présent dans le premier CTE table. Mais si ce n'est pas permis, je vais essayer:
WITH (NodeChildren) AS
{
--initialization
SELECT ParentNodeID, NodeID, 1 AS GenerationsRemoved
FROM Nodes
UNION ALL
--recursive execution
SELECT parents.ParentNodeID, Nodes.NodeID, parents.Generations+1
FROM NodeChildren parents
INNER JOIN Nodes
ON parents.NodeID = nodes.ParentNodeID
}
Mais qui échoue parce que ce n'est pas seulement le rejoindre sur le récursive éléments, mais conserve recursivly en ajoutant les mêmes lignes:
Msg 530, Level 16, State 1, Line 1
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Dans SQL Server 2000, j'ai simulé un CTE en utilisant une Fonction Définie par l'Utilisateur (UDF):
CREATE FUNCTION [dbo].[fn_NodeChildren] ()
RETURNS @Result TABLE (
ParentNodeID int NULL,
ChildNodeID int NULL,
Generations int NOT NULL)
AS
/*This UDF returns all "ParentNode" - "Child Node" combinations
...even multiple levels separated
BEGIN
DECLARE @Generations int
SET @Generations = 1
--Insert into the Return table all "Self" entries
INSERT INTO @Result
SELECT ParentNodeID, NodeID, @Generations
FROM Nodes
WHILE @@rowcount > 0
BEGIN
SET @Generations = @Generations + 1
--Add to the Children table:
-- children of all nodes just added
-- (i.e. Where @Result.Generation = CurrentGeneration-1)
INSERT @Result
SELECT CurrentParents.ParentNodeID, Nodes.NodeID, @Generations
FROM Nodes
INNER JOIN @Result CurrentParents
ON Nodes.ParentNodeID = CurrentParents.ChildNodeID
WHERE CurrentParents.Generations = @Generations - 1
END
RETURN
END
Et de la magie pour l'empêcher de faire exploser a été la limitation de la clause where:
OÙ CurrentParents.Générations - @Générations-1
Comment empêcher une expression de table commune récursive à partir de recursing pour toujours?
source d'informationauteur Ian Boyd
Vous devez vous connecter pour publier un commentaire.
Essayez ceci:
Essentiellement de retirer le "montrez-moi uniquement absolue parents" dès l'initialisation de la requête; de Cette façon, il génère les résultats à partir de chacun d'eux et en descente à partir de là. J'ai également ajouté dans la section "OÙ P. GenerationsRemoved <= 10" comme une récursivité infinie catch(remplacer 10 avec un nombre jusqu'à 100 en fonction de vos besoins). Ajoutez ensuite le tri donc, il semble que les résultats que vous vouliez.
De côté: avez-vous de SQL Server 2008? Cela peut être adaptée à la
hierarchyid
type de données.Si j'ai bien compris vos intentions, vous pouvez obtenir vous entraîner en faisant quelque chose comme ceci:
Ce sera le retour de la génération de différence entre tous les nœuds, vous pouvez le modifier pour vous besoins exacts.
Bien, votre réponse n'est pas tout à fait évidentes 🙂
Cette partie est appelée le "point d'ancrage" de la partie de l'expression de table commune récursive - mais il faut vraiment en choisir une seule ou de quelques lignes de votre table - cette sélectionne tout!
Je suppose que ce que vous manquez ici est tout simplement approprié clause where:
Cependant, je crains que votre condition d'avoir non seulement la "droite" de la hiérarchie, mais aussi le grand-parent-enfant, les lignes, peut-être pas facile à satisfaire.... normalement expression de table commune récursive ne jamais montrer un niveau et ses subordonnés directs (et que le bas de la hiérarchie, bien sûr) - il n'a pas l'habitude de sauter un, deux ou même plus de niveaux.
Espère que cela aide un peu.
Marc
Le problème est avec le Sql Server par défaut de la limite de la récursivité (100). Si vous essayez votre exemple en haut avec l'ancrage de restriction supprimée (aussi ajouté Par Ordre):
Ce produit les résultats escomptés. Le problème de l'aar face est avec un plus grand nombre de lignes que vous recirse plus de 100 fois ce qui est une limite par défaut. Cela peut être modifié par l'ajout de
option (max recursion x)
après votre requête, où x est un nombre compris entre 1 et 32767. x peut également être réglé à 0 qui ne définit aucune limite cependant pourrait très vite avoir un effet très négatif sur les performances du serveur. Clairement que le nombre de lignes de Nœuds augmente, le nombre de récurrences sera peut augmenter très rapidement et je voudrais éviter cette approche, à moins qu'il y avait une limite supérieure sur les lignes dans la table. Pour être complet, le final de la requête devrait ressembler à:Où 32767 pourrait être ajusté à la baisse pour s'adapter à votre scénario
Avez-vous essayé la construction d'un chemin d'accès dans la CCE et de l'utiliser pour identifier les ancêtres?
Vous pouvez ensuite soustraire le descendant du nœud de profondeur à partir de l'ancêtre du nœud de profondeur pour calculer la GenerationsRemoved colonne, comme si...
Ce casse la récursivité de la limite imposée sur Chris Shaffer réponse.
J'ai créer une table avec un cycle:
Dans les cas où il existe un potentiel de cycle (c'est à dire ParentNodeId n'EST PAS NULLE), la génération de suppression est commencé à 2. Nous pouvons alors l'identité de cycles de vérification (P. ParentNodeID == N. NodeID), ce qui nous n'ont tout simplement pas l'ajouter. Ensuite, on ajoute l'omis de génération de supprimer = 1.
Mon exemple montre ici une expression de table commune récursive que la récursivité s'arrête au bout de 100 niveaux (le max). En prime, il affiche un tas de caractères ASCII et de la valeur numérique.