Aplatir contiguïté Liste hiérarchie à une liste de tous les chemins
-
11-09-2019 - |
Question
J'ai une table qui stocke des informations hiérarchiques en utilisant le modèle Liste contiguïté. (Utilise une clé d'auto-référentiel. - exemple ci-dessous Ce tableau peut paraître familier ):
category_id name parent
----------- -------------------- -----------
1 ELECTRONICS NULL
2 TELEVISIONS 1
3 TUBE 2
4 LCD 2
5 PLASMA 2
6 PORTABLE ELECTRONICS 1
7 MP3 PLAYERS 6
8 FLASH 7
9 CD PLAYERS 6
10 2 WAY RADIOS 6
Quelle est la meilleure méthode pour "aplatir" les données ci-dessus dans quelque chose comme ça?
category_id lvl1 lvl2 lvl3 lvl4
----------- ----------- ----------- ----------- -----------
1 1 NULL NULL NULL
2 1 2 NULL NULL
6 1 6 NULL NULL
3 1 2 3 NULL
4 1 2 4 NULL
5 1 2 5 NULL
7 1 6 7 NULL
9 1 6 9 NULL
10 1 6 10 NULL
8 1 6 7 8
Chaque ligne est un « chemin » dans la hiérarchie, sauf qu'il ya une ligne pour chaque noeud (et pas seulement chaque nœud feuille ). La colonne category_id représente le nœud courant et les colonnes « LVL » sont ses ancêtres. La valeur du nœud actuel doit également être dans la colonne lvl le plus à droite. La valeur dans la colonne lvl1 toujours représenter le nœud racine, les valeurs lvl2 toujours représenter les descendants directs de lvl1, et ainsi de suite.
Si possible, la méthode pour générer cette sortie serait en SQL, et travaillerait pour les hiérarchies n-tiers.
La solution
Pour faire des requêtes à plusieurs niveaux à travers une simple liste de contiguïté implique invariablement l'auto-gauche rejoint. Il est facile de faire une table aligné à droite:
SELECT category.category_id,
ancestor4.category_id AS lvl4,
ancestor3.category_id AS lvl3,
ancestor2.category_id AS lvl2,
ancestor1.category_id AS lvl1
FROM categories AS category
LEFT JOIN categories AS ancestor1 ON ancestor1.category_id=category.category_id
LEFT JOIN categories AS ancestor2 ON ancestor2.category_id=ancestor1.parent
LEFT JOIN categories AS ancestor3 ON ancestor3.category_id=ancestor2.parent
LEFT JOIN categories AS ancestor4 ON ancestor4.category_id=ancestor3.parent;
à gauche aligner comme votre exemple est un peu plus délicat. Cela vient à l'esprit:
SELECT category.category_id,
ancestor1.category_id AS lvl1,
ancestor2.category_id AS lvl2,
ancestor3.category_id AS lvl3,
ancestor4.category_id AS lvl4
FROM categories AS category
LEFT JOIN categories AS ancestor1 ON ancestor1.parent IS NULL
LEFT JOIN categories AS ancestor2 ON ancestor1.category_id<>category.category_id AND ancestor2.parent=ancestor1.category_id
LEFT JOIN categories AS ancestor3 ON ancestor2.category_id<>category.category_id AND ancestor3.parent=ancestor2.category_id
LEFT JOIN categories AS ancestor4 ON ancestor3.category_id<>category.category_id AND ancestor4.parent=ancestor3.category_id
WHERE
ancestor1.category_id=category.category_id OR
ancestor2.category_id=category.category_id OR
ancestor3.category_id=category.category_id OR
ancestor4.category_id=category.category_id;
travaillerait pour les hiérarchies n-tiers.
Désolé, les requêtes de profondeur arbitraire ne sont pas possibles dans le modèle contiguïté liste. Si vous faites ce genre de requête beaucoup, vous devez changer votre schéma à l'un des
Autres conseils
Comme mentionné précédemment, SQL n'a aucun moyen propre à mettre en œuvre des tables avec des variables dynamiquement le nombre de colonnes. Les deux seules solutions que j'ai utilisées avant sont les suivants: 1. Un nombre fixe d'auto-joint, ce qui donne un nombre fixe de colonnes (AS par BobInce) 2. Générer les résultats sous forme de chaîne dans une seule colonne
La seconde semble grotesque au départ; stocker les ID comme chaîne ?! Mais lorsque la sortie est formaté en XML ou quelque chose, les gens ne semblent pas à l'esprit tant.
De même, cela est très peu utile si vous voulez ensuite rejoindre sur les résultats dans SQL. Si le résultat doit être fourni à une application, il peut être très approprié. Mais, personnellement, je préfère faire l'aplanissement dans l'application plutôt que SQL
Je suis coincé ici sur un écran de 10 pouces sans accès à SQL je ne peux pas donner le code testé, mais la méthode de base serait d'utiliser récursion d'une certaine façon;
- Une fonction scalaire récursive peut faire
- MS SQL peut le faire en utilisant une récursif WITH (plus efficace)
Fonction Scalar (quelque chose comme):
CREATE FUNCTION getGraphWalk(@child_id INT)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE @graph VARCHAR(4000)
-- This step assumes each child only has one parent
SELECT
@graph = dbo.getGraphWalk(parent_id)
FROM
mapping_table
WHERE
category_id = @child_id
AND parent_id IS NOT NULL
IF (@graph IS NULL)
SET @graph = CAST(@child_id AS VARCHAR(16))
ELSE
SET @graph = @graph + ',' + CAST(@child_id AS VARCHAR(16))
RETURN @graph
END
SELECT
category_id AS [category_id],
dbo.getGraphWalk(category_id) AS [graph_path]
FROM
mapping_table
ORDER BY
category_id
Je ne l'ai pas utilisé une récursif dans un certain temps, mais je vais donner la syntaxe d'un aller même si je n'ai pas SQL ici pour tester quoi que ce soit:)
récursif
WITH
result (
category_id,
graph_path
)
AS
(
SELECT
category_id,
CAST(category_id AS VARCHAR(4000))
FROM
mapping_table
WHERE
parent_id IS NULL
UNION ALL
SELECT
mapping_table.category_id,
CAST(result.graph_path + ',' + CAST(mapping_table.category_id AS VARCHAR(16)) AS VARCHAR(4000))
FROM
result
INNER JOIN
mapping_table
ON result.category_id = mapping_table.parent_id
)
SELECT
*
FROM
result
ORDER BY
category_id
EDIT - SORTIE pour les deux est le même:
1 '1'
2 '1,2'
3 '1,2,3'
4 '1,2,4'
5 '1,2,5'
6 '1,6'
7 '1,6,7'
8 '1,6,7,8'
9 '1,6,9'
Traversant un arbre de profondeur arbitraire implique généralement le code de procédure récursive, à moins que vous utilisez des fonctions spéciales de certains SGBD.
Dans Oracle, la clause CONNECT BY vous permettra de traverser l'arbre en profondeur premier ordre si vous utilisez la liste de contiguïté, comme vous l'avez fait ici.
Si vous utilisez des ensembles imbriqués, le numéro de séquence gauche vous fournira l'ordre de visiter les nœuds.
peut-être fait avec SQL dynamique dans une procédure de magasins. Vous devenez alors limité à ce qui peut être fait sith la procédure stockée. De toute évidence, il devient un défi à exec les résultats dans une table temporaire ne sachant pas combien de colonnes à attendre. Toutefois, si l'objectif est de sortie vers une page Web ou une autre interface utilisateur, alors il est peut-être vaut la peine ...