J'écris une structure d'arborescence de données qui est combinée à partir d'un Tree et d'un TreeNode. L'arbre contiendra la racine et les actions de niveau supérieur sur les données ..___ J'utilise une bibliothèque d'interface utilisateur pour présenter l'arbre sous une forme Windows où je peux lier l'arbre à TreeView.
J'aurai besoin de sauvegarder cette arborescence et ses noeuds dans la base de données . Quel sera le meilleur moyen de sauvegarder cette arborescence et d'obtenir les fonctionnalités suivantes:
J'ai eu 2 idées. La première consiste à sérialiser les données dans une ligne d'une table ..__ La seconde consiste à enregistrer dans les tables, mais ensuite, lors du passage aux entités de données, je perdrai les états de ligne de la table sur les nœuds modifiés.
Des idées?
L'implémentation la plus facile est la structure adjacency list:
id parent_id data
Cependant, certaines bases de données, en particulier MySQL
, rencontrent des problèmes lors de la gestion de ce modèle, car il nécessite la capacité d'exécuter des requêtes récursives dont MySQL
est absent.
Un autre modèle est nested sets:
id lft rgt data
où lft
et rgt
sont des valeurs arbitraires qui définissent la hiérarchie (tout lft
, rgt
de l'enfant doit être compris dans lft
, rgt
de tout parent)
Cela ne nécessite pas de requêtes récursives, mais plus lent et plus difficile à gérer.
Cependant, dans MySQL
, ceci peut être amélioré en utilisant SPATIAL
abitilies.
Voir ces articles dans mon blog:
pour des explications plus détaillées.
J'ai mis cette page en favori sur SQL-Antipatterns, qui traite de plusieurs alternatives: http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back?src=embed
La recommandation à partir de là est d'utiliser une table de fermeture (c'est expliqué dans les diapositives).
Voici le résumé (diapositive 77):
| Query Child | Query Subtree | Modify Tree | Ref. Integrity
Adjacency List | Easy | Hard | Easy | Yes
Path Enumeration | Easy | Easy | Hard | No
Nested Sets | Hard | Easy | Hard | No
Closure Table | Easy | Easy | Easy | Yes
Je suis surpris que personne n'ait mentionné le chemin matérialisé solution, qui est probablement le moyen le plus rapide de travailler avec des arbres en SQL standard.
Dans cette approche, chaque nœud de l'arborescence a une colonne chemin, où le chemin complet de la racine au nœud est stocké. Cela implique des requêtes très simples et rapides.
Regardez l'exemple de table node:
+---------+-------+
| node_id | path |
+---------+-------+
| 0 | |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 1.4 |
| 5 | 2.5 |
| 6 | 2.6 |
| 7 | 2.6.7 |
| 8 | 2.6.8 |
| 9 | 2.6.9 |
+---------+-------+
Pour obtenir les enfants du noeud x, vous pouvez écrire la requête suivante:
SELECT * FROM node WHERE path LIKE CONCAT((SELECT path FROM node WHERE node_id = x), '.%')
N'oubliez pas que la colonne chemin doit être indexée, afin de fonctionner rapidement avec la clause LIKE.
Si vous utilisez PostgreSQL, vous pouvez utiliser ltree
, un package de l'extension contrib (fourni par défaut) qui implémente la structure de données de l'arborescence.
De la docs :
CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_Gist_idx ON test USING Gist (path);
CREATE INDEX path_idx ON test USING BTREE (path);
Vous pouvez faire des requêtes comme:
ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
path
------------------------------------
Top.Science
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(4 rows)
Cela dépend de la manière dont vous allez interroger et mettre à jour les données. Si vous stockez toutes les données sur une seule ligne, il s’agit en fait d’une seule unité dans laquelle vous ne pouvez pas interroger ou qui met à jour partiellement sans réécrire toutes les données.
Si vous souhaitez stocker chaque élément sous forme de ligne, vous devez d'abord lire Gestion des données hiérarchiques dans MySQL (spécifique à MySQL, mais les conseils sont valables pour de nombreuses autres bases de données).
Si vous n'accédez jamais qu'à une arborescence complète, le modèle de liste de contiguïté rend difficile l'extraction de tous les nœuds situés sous la racine sans utiliser de requête récursive. Si vous ajoutez une colonne supplémentaire qui renvoie à la tête, vous pouvez créer SELECT * WHERE head_id = @id
et obtenir l’arbre complet dans une requête non récursive, mais cela dénormalise la base de données.
Certaines bases de données ont des extensions personnalisées qui facilitent le stockage et la récupération de données hiérarchiques, par exemple Oracle possède CONNECT BY .
Comme il s'agit de la meilleure réponse à la question "d'arbres sql" dans une recherche google, je vais essayer de le mettre à jour du point de vue d'aujourd'hui (décembre 2018).
La plupart des réponses impliquent que l'utilisation d'une liste de contiguïté est à la fois simple et lente et recommandent donc d'autres méthodes.
Depuis la version 8 (publiée en avril 2018), MySQL supporte les expressions de table communes récursives (CTE) . MySQL est un peu en retard pour le spectacle mais cela ouvre une nouvelle option.
Il existe un didacticiel ici qui explique l’utilisation de requêtes récursives pour gérer une liste de contiguïté.
Comme la récursion est maintenant entièrement exécutée dans le moteur de base de données, elle est beaucoup plus rapide que par le passé (lorsqu'elle devait s'exécuter dans le moteur de script).
Le blog here donne quelques mesures (qui sont à la fois biaisées et postgres au lieu de MySQL) mais montre néanmoins que les listes d’adjacence ne doivent pas nécessairement être lentes.
Donc, ma conclusion aujourd'hui est la suivante:
Le meilleur moyen, à mon avis, consiste à attribuer à chaque nœud un identifiant et un parent_id, où l'identifiant parent est l'identifiant du noeud parent. Cela a quelques avantages
Quelque chose comme une table "nœuds" où chaque ligne de nœud contient un identifiant parent (en plus des données de nœud ordinaires). Pour root, le parent est NULL.
Bien sûr, cela prend un peu plus de temps à trouver des enfants, mais de cette manière, la base de données elle-même sera assez simple.