2010-05-14 7 views
2

J'ai beaucoup de difficulté à construire une requête qui renvoie une hiérarchie de style XML.Commande de jeu de résultats récursif dans SQL Server

Nous avons une table de base de données qui contient une hiérarchie d'URL pour notre site Web. La table contient les colonnes: ID, URL, DisplayName, ParentID, ItemOrder

L'ID parent forme une relation récursive entre l'élément en cours et son parent. L'élément doit être situé en dessous de son parent dans la hiérarchie et il doit également être ordonné en utilisant l'ordre des éléments par rapport aux éléments du même niveau dans la hiérarchie.

J'ai réussi à faire fonctionner une requête récursive de façon à ce qu'elle descende séquentiellement la hiérarchie, mais je ne peux pas non plus la trier par ordre d'élément.

Ma requête en cours est ci-dessous:

WITH Parents AS 
(
SELECT MenuItemId, URL, ParentItemId, ItemOrder 
FROM CambsMenu 

UNION ALL 

SELECT si.MenuItemId, si.URL, si.ParentItemId, si.ItemOrder 
FROM CambsMenu si INNER JOIN Parents p 
ON si.ParentItemId = p.MenuItemId 
) 

SELECT DISTINCT * 
FROM Parents 
+0

Pouvez-vous être plus sepcific de l'ordre? N'ajoute pas 'ORDER BY ItemOrder' au travail SELECT de niveau supérieur? – mdma

+0

J'ai moi-même eu du mal avec ça - vous pouvez commander par de nombreuses choses - le MenuItemId, le niveau dans la hiérarchie et quelques autres - mais je n'ai jamais trouvé de manière satisfaisante en T-SQL pour que les enfants viennent après leurs parents, plusieurs niveaux profonds. Je suis impatient de voir si quelqu'un ici a une bonne idée - bonne question! –

+0

une autre (pour moi) solution de travail [là] (https://stackoverflow.com/a/18771942/3588062) avec HIERARCHYID (travaillant à partir de SQL Server 2008 R2) –

Répondre

2

Le nombre de frères et soeurs est-il une valeur connue? Le nombre de niveaux est-il connu? Si tel est le cas, vous pouvez effectuer des opérations sur ItemOrder, pour garantir que chaque élément possède un ItemOrder unique, puis trier simplement par cette valeur. Par exemple, supposons qu'un élément ne puisse pas avoir plus de 10 enfants (ItemOrder s'étend de 0 à 9) et qu'il y a au plus 5 niveaux. Qu'est-ce que je vais faire maintenant, est de faire le premier parent ItemOrder à 10000 fois son ordre actuel des articles, et il est childer ItemOrder serait 1000 fois son ItemOrder actuel plus son parent ItemOrder, et ainsi de suite, en supprimant un 0 à chaque fois vous allez un niveau bas.

WITH Parents AS 
(
SELECT MenuItemId, 
    URL, 
    ParentItemId, 
    (ItemOrder * 10000) AS ItemOrder, 
    10000 AS Multiplier 
FROM CambsMenu 
WHERE ParentItemId IS NULL 

UNION ALL 

SELECT si.MenuItemId, 
    si.URL, 
    si.ParentItemId, 
    (p.ItemOrder + si.ItemOrder * p.Multiplier/ 10) as ItemOrder, 
    (p.Multiplier/10) as Multiplier 
FROM CambsMenu si INNER JOIN Parents p 
ON si.ParentItemId = p.MenuItemId 
) 

SELECT * FROM Parents ORDER BY ItemOrder 

Si le nombre de niveaux ou des enfants est inconnu, vous pouvez aller avec une approche similaire, mais au lieu de construire un ItemOrder numérique vous pouvez construire une ItemOrder chaîne, garantissant que la chaîne « 10/01/20 » est inférieure à la chaîne '2.1'

+0

Merci pour votre aide les gars. J'ai essayé de le faire en utilisant des chaînes comme vous l'avez suggéré Fede mais le problème que j'ai trouvé était que le 10.1 est classé avant quelque chose comme 2.1 parce que les nombres sont classés avant toute autre valeur de chaîne. Il se peut que je devrais le remplir avec les zéros précédents. – Ben

+0

@Ben, oui ce sera toujours un peu un problème; – Unreason

1
WITH Parents AS 
(
SELECT MenuItemId, URL, ParentItemId, ItemOrder, 0 AS Level, Cast((ItemOrder+1000) as Varchar(MAX)) as MatPath 
FROM CambsMenu 
WHERE ParentItemId IS NULL 

UNION ALL 

SELECT si.MenuItemId, si.URL, si.ParentItemId, si.ItemOrder, Level + 1, MathPath + '.' CAST((si.ItemOrder+1000) as Varchar(MAX) 
FROM CambsMenu si INNER JOIN Parents p 
ON si.ParentItemId = p.MenuItemId 
) 

SELECT DISTINCT * 
FROM Parents 
ORDER BY MatPath 

EDIT: Réponse mise à jour, était à l'origine de tri par niveau, ce qui n'a pas été posée. En outre, la réponse n'est pas testée. Mis à jour à nouveau, la requête initiale ne filtrait pas sur IS NULL

EDIT2: Voici une mise à jour qui utilisera des flottants et des sous-requêtes pour obtenir le nombre maximal de feuilles/branches; on suppose que ItemOrder est ascendant, en commençant par 1, sans trous et qu'il est redémarré pour chaque parent. Cela pourrait être reconverti en utilisant des entiers, car alors il sera plus évident que le tri peut déborder/perdre de la précision avec le nombre de niveaux.

WITH Hierarchy AS 
(
    SELECT MenuItemID, 
     URL, 
     ParentItemId, 
     ItemOrder, 
     0 as level, 
     cast(1 as float) as hord 
    FROM CambsMenu 
    WHERE ParentItemId IS NULL 
UNION ALL 
    SELECT r.MenuItemId, 
     r.URL, 
     r.PrentItemId, 
     r.ItemOrder, 
     h.level + 1, 
     h.hord + r.ItemOrder/power(
      (SELECT MAX(n)+1 
      FROM (SELECT count(*) AS n 
        FROM CambsMenu 
        GROUP BY ParentItemId) t), h.level+1) 
    FROM CambsMenu r INNER JOIN Hierarchy h 
    ON r.ParentItemId = h.MenuItemId 
) 
SELECT * 
FROM Hierarchy 
ORDER BY hord; 
+0

Mais dans ce cas, vous obtenez tous les éléments sur le niveau 0, puis tous au niveau 1, puis tous au niveau 2 et ainsi de suite. Vous n'obtiendrez pas le parent 0, puis tous ses enfants de niveau 1, et leurs petits-enfants de niveau 2 inclus ..... –

+0

Merci pour la réponse. Marc a raison, l'ordre est incorrect et j'ai aussi trouvé que le niveau n'est pas incrémenté correctement. – Ben

+1

@marc_s, oui j'ai réalisé que j'ai mal lu la question, mis à jour pour construire le chemin matérialisé (avec l'hypothèse de max 8999 articles par branche) – Unreason

0

SQL ne supporte pas un type 'hiérarchie' ou 'arbre' ou 'graphique', car SQL/le modèle relationnel ont été essentiellement inventés dans le but de rendre obsolètes ces types.

Vous avez écrit une requête qui calcule ce qui est connu en termes mathématiques comme une "fermeture transitive". Je doute que c'est vraiment ce que tu veux. Si une relation ("table") a les paires (1 2) et (2 3), alors votre requête inclura la paire résultante (1 3). Cependant, (dans cet exemple) je suppose que vous ne voudriez pas que votre résultat de style XML inclue une étiquette portant le numéro 3 comme un enfant direct du numéro 1 ...

Je soupçonne que ce que vous voulez est plus probable à atteindre en utilisant l'opérateur GROUP de l'algèbre relationnelle.Avertissement: ce n'est pas vraiment la même chose que "GROUP BY" (l'opérateur GROUP de l'algèbre relationnelle produit des tables contenant des colonnes dont la valeur est elle-même une table - par exemple une table contenant tous les enfants directs d'un parent). tout à fait probable que votre SGBD particulier ne le supporte pas, dans ce cas, vous êtes laissé à peu près « abandonnée par votre SGBD » et « pas d'autre choix que de coder toute la merde foutue (par ce que je veux dire la récursion en particulier) toi même".

+1

SQL prend en charge la récursivité dans les nouvelles normes, sa requête ne renverrait pas de "fermeture transitive" (comme vous avez le champ ItemOrder, vous pouvez utiliser une approche similaire à ma dernière réponse et précalculer l'ordre de tri (si vos données sont rarement mises à jour/insérées). recherche la récursivité et élabore la question, la requête récursive se joint à parentid = id) et il utilise DISTINCT pour qu'il se débrouille bien. En outre, il existe d'autres façons de gérer les hiérarchies dans les systèmes relationnels (http://onlamp.com/pub/a/onlamp/2004/08/05/hierarchical_sql.html). En outre, une affirmation que le modèle relationnel est inventé pour rendre ces types obsolètes est faux; bien qu'il aborde les problèmes que le réseau et le modèle hiérarchique avaient. Cependant, il n'y a rien de mal avec les relations hiérarchiques par eux-mêmes. – Unreason

4

Merci pour toutes vos réponses!

Juste pour être complet, voici la solution finale que je suis venu avec. Il crée une chaîne qui est séparée en sous-sections par des points. La solution ci-dessous ne soutiendra que jusqu'à 9999 éléments dans le nœud racine mais vous pouvez facilement étendre en augmentant le nombre des principaux zéros en changeant simplement le numéro dans la commande

WITH Parents AS 
(
SELECT MenuItemId, 
    URL, 
    ParentItemId, 
    DisplayName, 
    OpenInNewWindow, 
    ItemOrder, 
    CAST((REPLACE(STR(ItemOrder,4),' ','0')) AS nvarchar(max)) AS OrderString 
FROM CambsMenu 
WHERE ParentItemId IS NULL 

UNION ALL 

SELECT si.MenuItemId, 
    si.URL, 
    si.ParentItemId, 
    si.DisplayName, 
    si.OpenInNewWindow, 
    si.ItemOrder, 
    (p.OrderString + '.' + CAST((REPLACE(STR(si.ItemOrder,4),' ','0')) AS nvarchar(max))) AS OrderString 
FROM CambsMenu si INNER JOIN Parents p 
ON si.ParentItemId = p.MenuItemId 
) 

SELECT * FROM Parents ORDER BY OrderString ASC 
+0

Comment faire, si vous n'avez pas besoin de surcharger ItemOrder tout le temps? Quoi qu'il en soit .. si toutes vos lignes ont ItemOrder, votre solution fera l'affaire. –

9

approche normale hiérarchique STR (ItemOrder, 4) :

select * 
into emp 
from 
(values 
(1, 'President', NULL), 
(2, 'Vice President', 1), 
(3, 'CEO', 2), 
(4, 'CTO', 2), 
(5, 'Group Project Manager', 4), 
(6, 'Project Manager 1', 5), 
(7, 'Project Manager 2', 5), 
(8, 'Team Leader 1', 6), 
(9, 'Software Engineer 1', 8), 
(10, 'Software Engineer 2', 8), 
(11, 'Test Lead 1', 6), 
(12, 'Tester 1', 11), 
(13, 'Tester 2', 11), 
(14, 'Team Leader 2', 7), 
(15, 'Software Engineer 3', 14), 
(16, 'Software Engineer 4', 14), 
(17, 'Test Lead 2', 7), 
(18, 'Tester 3', 17), 
(19, 'Tester 4', 17), 
(20, 'Tester 5', 17) 
) as x(emp_id, emp_name, mgr_id) 

Query:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as 
(
select 
    a.emp_id, a.emp_name, 0, a.mgr_id, 
    a.emp_name 
from emp a 
where a.mgr_id is null 

union all 

select 
    b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

    sort || ' : ' || b.emp_name 

from emp b 
join org on org.emp_id = b.mgr_id 
) 
select 
emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name, sort 
from org 
order by sort 

sortie:

emp_id |   emp_name    |              sort               
--------+---------------------------------+-------------------------------------------------------------------------------------------------------------------- 
     1 | President      | President 
     2 | Vice President    | President : Vice President 
     3 |  CEO       | President : Vice President : CEO 
     4 |  CTO       | President : Vice President : CTO 
     5 |  Group Project Manager  | President : Vice President : CTO : Group Project Manager 
     6 |   Project Manager 1  | President : Vice President : CTO : Group Project Manager : Project Manager 1 
     8 |   Team Leader 1   | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1 
     9 |    Software Engineer 1 | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1 : Software Engineer 1 
    10 |    Software Engineer 2 | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1 : Software Engineer 2 
    11 |   Test Lead 1   | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1 
    12 |    Tester 1   | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1 : Tester 1 
    13 |    Tester 2   | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1 : Tester 2 
     7 |   Project Manager 2  | President : Vice President : CTO : Group Project Manager : Project Manager 2 
    14 |   Team Leader 2   | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2 
    15 |    Software Engineer 3 | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2 : Software Engineer 3 
    16 |    Software Engineer 4 | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2 : Software Engineer 4 
    17 |   Test Lead 2   | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 
    18 |    Tester 3   | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 3 
    19 |    Tester 4   | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 4 
    20 |    Tester 5   | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 5 
(20 rows) 

Maintenant, nous allons outrepasser le tri sur les gestionnaires de projet de groupe, faisons Chef de projet 2 viennent avant 1, et chef de projet 1 viennent après Chef de projet 2. Faisons également tester 4 est avant 3 et testeur 3 vient après testeur 4

alter table emp add column order_override int null; 

update emp set order_override = 1 where emp_id = 7; -- PM 2 
update emp set order_override = 2 where emp_id = 6; -- PM 1 

update emp set order_override = 1 where emp_id = 19; -- Tester 4 
update emp set order_override = 2 where emp_id = 18; -- Tester 3 

requête:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as 
(
select 
    a.emp_id, a.emp_name, 0, a.mgr_id, 
    a.emp_name 
from emp a 
where a.mgr_id is null 

union all 

select 
    b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

    sort || ' : ' || coalesce(lpad(order_override::text, 10, '0'), b.emp_name) 
from emp b 
join org on org.emp_id = b.mgr_id 
) 
select 
emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name, sort 
from org 
order by sort 

sortie:

emp_id |   emp_name    |             sort              
--------+---------------------------------+------------------------------------------------------------------------------------------------------------- 
     1 | President      | President 
     2 | Vice President    | President : Vice President 
     3 |  CEO       | President : Vice President : CEO 
     4 |  CTO       | President : Vice President : CTO 
     5 |  Group Project Manager  | President : Vice President : CTO : Group Project Manager 
     7 |   Project Manager 2  | President : Vice President : CTO : Group Project Manager : 0000000001 
    14 |   Team Leader 2   | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2 
    15 |    Software Engineer 3 | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2 : Software Engineer 3 
    16 |    Software Engineer 4 | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2 : Software Engineer 4 
    17 |   Test Lead 2   | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 
    19 |    Tester 4   | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : 0000000001 
    18 |    Tester 3   | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : 0000000002 
    20 |    Tester 5   | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : Tester 5 
     6 |   Project Manager 1  | President : Vice President : CTO : Group Project Manager : 0000000002 
     8 |   Team Leader 1   | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1 
     9 |    Software Engineer 1 | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1 : Software Engineer 1 
    10 |    Software Engineer 2 | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1 : Software Engineer 2 
    11 |   Test Lead 1   | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1 
    12 |    Tester 1   | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1 : Tester 1 
    13 |    Tester 2   | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1 : Tester 2 
(20 rows) 

Wi Thout la colonne de tri en projection de données:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as 
(
select 
    a.emp_id, a.emp_name, 0, a.mgr_id, 
    a.emp_name 
from emp a 
where a.mgr_id is null 

union all 

select 
    b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

    sort || ' : ' || coalesce(lpad(order_override::text, 10, '0'), b.emp_name) 
from emp b 
join org on org.emp_id = b.mgr_id 
) 
select 
emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name 
from org 
order by sort 

Sortie:

emp_id |   emp_name    
--------+--------------------------------- 
     1 | President 
     2 | Vice President 
     3 |  CEO 
     4 |  CTO 
     5 |  Group Project Manager 
     7 |   Project Manager 2 
    14 |   Team Leader 2 
    15 |    Software Engineer 3 
    16 |    Software Engineer 4 
    17 |   Test Lead 2 
    19 |    Tester 4 
    18 |    Tester 3 
    20 |    Tester 5 
     6 |   Project Manager 1 
     8 |   Team Leader 1 
     9 |    Software Engineer 1 
    10 |    Software Engineer 2 
    11 |   Test Lead 1 
    12 |    Tester 1 
    13 |    Tester 2 
(20 rows) 

Project Manager 2 est avant Project Manager 1. Tester 4 est avant testeur 3

La technique réside dans la substitution de texte numérique pour b.name s'il y a un order_override (non nul):

sort || ' : ' || coalesce(lpad(order_override::text, 10, '0'), b.emp_name) 

Ab Code Ove est Postgres, de se convertir à Sql Server, supprimer le mot RECURSIVE, changer REPEAT-REPLICATE, ||-+.

équivalent de ...

lpad(order_override::text, 10, '0') 

... est:

RIGHT(REPLICATE('0',10) + CONVERT(VARCHAR, order_override), 10) 
1

Bien que ce soit un ancien poste, je n'ai pas vu cette réponse encore donnée, et il n » Cela semble avoir les inconvénients que certaines autres réponses ont. Je recommande d'utiliser la fonction RANK() pour commander correctement votre jeu récursif de résultat. Cette méthode est un peu plus indulgente avec les données de Wilder.Cette solution suppose que vous ne disposerez pas de plus de 99 sous-sous-résultats sous n'importe quel résultat dans votre récursivité, mais il peut facilement être étendu si vous en avez des milliers, des millions ou même plus. Modifiez-le pour qu'il fonctionne avec votre ensemble de données.

WITH Forms 
      AS (
       SELECT FormId, 
         CAST(Caption AS VARCHAR(MAX)) AS Caption, 
         1 AS Depth, 
         CAST('01' AS VARCHAR(MAX)) AS [Rank] 
       FROM  fx_NavTree 
       WHERE  ParentFormId IS NULL 
       UNION ALL 
       SELECT nt.FormId, 
         CAST(SPACE(f.Depth * 2) + nt.Caption AS VARCHAR(MAX)) AS Caption, 
         f.Depth + 1 AS Depth, 
         f.[Rank] + '-' + RIGHT('00' + CAST(RANK() OVER (PARTITION BY f.[Rank] ORDER BY nt.SortOrder) AS VARCHAR(MAX)), 2) AS [Rank] 
       FROM  fx_NavTree AS nt 
       INNER JOIN Forms AS f ON nt.ParentFormId = f.FormId 
      ) 
    SELECT * 
    FROM Forms 
    ORDER BY Forms.[Rank]; 

Dans le cas de Ben, il essaierait de RANK() la colonne ItemOrder. Sa solution devrait ressembler à ceci:

WITH Parents AS 
(
SELECT MenuItemId, 
     CAST(URL as VARCHAR(MAX)) as URL, 
     ParentItemId, 
     CAST(ItemOrder AS VARCHAR(MAX)) as ItemOrder 
FROM CambsMenu 

UNION ALL 

SELECT si.MenuItemId, 
     CAST(si.URL AS VARCHAR(MAX)) as URL, 
     si.ParentItemId, 
     p.ItemOrder + '-' + RIGHT('00' + CAST(RANK() OVER (PARTITION BY 
p.ItemOrder ORDER BY si.ItemOrder) AS VARCHAR(MAX)), 2) AS ItemOrder 
FROM CambsMenu si INNER JOIN Parents p 
ON si.ParentItemId = p.MenuItemId 
) 

SELECT DISTINCT * 
FROM Parents 
Questions connexes