Recherche de chapelure pour les ensembles imbriqués
-
03-07-2019 - |
Question
J'utilise des ensembles imbriqués (c.-à-d. traversée d'arborescence de pré-ordre modifiée) pour stocker une liste de groupes, et j'essaie de trouver un moyen rapide de générer un fil d'Ariane (sous forme de chaîne, pas de table) pour TOUS les groupes. immediatement. Mes données sont également stockées à l'aide du modèle de liste de contiguïté (des déclencheurs permettent de garder les deux synchronisés).
Ainsi, par exemple:
ID Name ParentId Left Right
0 Node A 0 1 12
1 Node B 0 2 5
2 Node C 1 3 4
3 Node D 0 6 11
4 Node E 3 7 8
5 Node F 4 9 9
Qui représente l'arbre:
- Nœud A
- Nœud B
- nœud C
- nœud D
- nœud E
- nœud F
- Nœud B
J'aimerais pouvoir disposer d'une fonction définie par l'utilisateur qui renvoie un tableau:
ID Breadcrumb
0 Node A
1 Node A > Node B
2 Node A > Node B > Node C
3 Node A > Node D
4 Node A > Node D > Node E
5 Node A > Node D > Node F
Pour rendre cela un peu plus compliqué (bien que ce soit en quelque sorte hors du champ de la question), j'ai également des restrictions d'utilisateurs à respecter. Ainsi, par exemple, si je n’ai accès qu’à id = 3, lors de l’exécution de la requête, j’obtiens:
ID Breadcrumb
3 Node D
4 Node D > Node E
5 Node D > Node F
J'ai une fonction définie par l'utilisateur qui prend un ID utilisateur en tant que paramètre et renvoie une table avec les identifiants de tous les groupes valides, de sorte que quelque part dans la requête
WHERE group.id IN (SELECT id FROM dbo.getUserGroups(@userid))
cela fonctionnera.
J'ai une fonction scalaire existante qui peut le faire, mais cela ne fonctionne tout simplement pas avec un nombre raisonnable de groupes (prend > 10 secondes sur 2 000 groupes). Il prend un identifiant de groupe et un identifiant d'utilisateur en tant que paramètre et renvoie un nvarchar. Il trouve les groupes de parents donnés (1 requête pour saisir les valeurs gauche / droite, une autre pour trouver les parents), limite la liste aux groupes auxquels l'utilisateur a accès (en utilisant la même clause WHERE que ci-dessus, donc une autre requête), et utilise ensuite un curseur pour parcourir chaque groupe et l'ajouter à une chaîne, avant de renvoyer cette valeur.
J'ai besoin d'une méthode pour ce faire qui fonctionnera rapidement (par exemple. < = 1s), à la volée.
C’est sur SQL Server 2005.
La solution 4
Ce que j'ai fini par faire est de créer une grande jointure qui lie simplement cette table à elle-même, encore et encore pour chaque niveau.
Je remplis d'abord une table @topLevelGroups avec uniquement les groupes de premier niveau (si vous ne possédez qu'une seule racine, vous pouvez ignorer cette étape), puis @userGroups avec les groupes que l'utilisateur peut voir.
SELECT groupid,
(level1
+ CASE WHEN level2 IS NOT NULL THEN ' > ' + level2 ELSE '' END
+ CASE WHEN level3 IS NOT NULL THEN ' > ' + level3 ELSE '' END
)as [breadcrumb]
FROM (
SELECT g3.*
,g1.name as level1
,g2.name as level2
,g3.name as level3
FROM @topLevelGroups g1
INNER JOIN @userGroups g2 ON g2.parentid = g1.groupid and g2.groupid <> g1.groupid
INNER JOIN @userGroups g3 ON g3.parentid = g2.groupid
UNION
SELECT g2.*
,g1.name as level1
,g2.name as level2
,NULL as level3
FROM @topLevelGroups g1
INNER JOIN @userGroups g2 ON g2.parentid = g1.groupid and g2.groupid <> g1.groupid
UNION
SELECT g1.*
,g1.name as level1
,NULL as level2
,NULL as level3
FROM @topLevelGroups g1
) a
ORDER BY [breadcrumb]
Ceci est un assez gros bidouillage, et est évidemment limité à un certain nombre de niveaux (pour mon application, il y a une limite raisonnable que je peux choisir), avec le problème que plus le nombre de niveaux est pris en charge, plus le nombre de rejoint exponentiellement, et est donc beaucoup plus lent.
Le faire dans le code est certes plus simple, mais pour moi, ce n’est tout simplement pas une option - j’ai parfois besoin de cette information directement à partir d’une requête SQL.
J'accepte cela comme une solution, car c'est ce que j'ai finalement fait et que cela pourrait fonctionner pour d'autres personnes. Cependant, si quelqu'un peut trouver une méthode plus efficace, je le lui changerai.
Autres conseils
voici le code SQL qui a fonctionné pour moi pour obtenir le & "fil d'Ariane &"; chemin depuis n’importe quel point de l’arbre. J'espère que ça aide.
SELECT ancestor.id, ancestor.title, ancestor.alias
FROM `categories` child, `categories` ancestor
WHERE child.lft >= ancestor.lft AND child.lft <= ancestor.rgt
AND child.id = MY_CURRENT_ID
ORDER BY ancestor.lft
Kath
Ok. Ceci concerne MySQL et non SQL Server 2005. Il utilise GROUP_CONCAT avec une sous-requête.
Ceci devrait renvoyer le fil d'Ariane complet sous forme de colonne unique.
SELECT
(SELECT GROUP_CONCAT(parent.name SEPARATOR ' > ')
FROM category parent
WHERE node.Left >= parent.Left
AND node.Right <= parent.Right
ORDER BY Left
) as breadcrumb
FROM category node
ORDER BY Left
Si vous le pouvez, utilisez un chemin (ou je pense l'avoir entendu parler de lignage) comme:
ID Name ParentId Left Right Path
0 Node A 0 1 12 0,
1 Node B 0 2 5 0,1,
2 Node C 1 3 4 0,1,2,
3 Node D 0 6 11 0,3,
4 Node E 3 7 8 0,3,4,
5 Node F 4 9 9 0,3,4,
Pour obtenir uniquement le noeud D et les suivants (psuedocode):
path = SELECT Path FROM Nodes WHERE ID = 3
SELECT * FROM Nodes WHERE Path LIKE = path + '%'
J'ai modifié la déclaration de Kathy pour obtenir la chapelure pour chaque élément
SELECT
GROUP_CONCAT(
ancestor.name
ORDER BY ancestor.lft ASC
SEPARATOR ' > '
),
child.*
FROM `categories` child
JOIN `categories` ancestor
ON child.lft >= ancestor.lft
AND child.lft <= ancestor.rgt
GROUP BY child.lft
ORDER BY child.lft
N'hésitez pas à ajouter une condition WHERE, par exemple
. WHERE ancestor.lft BETWEEN 6 AND 11
pas de code spécifique au serveur SQL, mais cherchez-vous simplement:
SELECT * FROM table WHERE left < (currentid.left) AND right > (currentid.right)