informatique:databases:mysql:stockage_utilisation_hierarchie
Différences
Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentesRévision précédente | |||
informatique:databases:mysql:stockage_utilisation_hierarchie [2024/11/21 18:37] – supprimée - modification externe (Date inconnue) 127.0.0.1 | informatique:databases:mysql:stockage_utilisation_hierarchie [2024/11/21 18:37] (Version actuelle) – ↷ Page déplacée de informatique:mysql:stockage_utilisation_hierarchie à informatique:databases:mysql:stockage_utilisation_hierarchie alexis | ||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
+ | ====== Stockage et utilisation d'une hiérarchie de données ====== | ||
+ | <WRAP info> | ||
+ | Cette page contient une traduction partielle d'un article proposé sur // | ||
+ | Malheureusement, | ||
+ | Cette traduction a été faite en utilisant [[https:// | ||
+ | J'ai essayé de reprendre la plus part du texte généré mais il se peut que j'ai oublié certains passages. | ||
+ | Certaines parties ont été complètement supprimées car elle étaient soit obsolètes soit inutiles. | ||
+ | </ | ||
+ | |||
+ | ===== Introduction ===== | ||
+ | La plupart des utilisateurs ont, à un moment ou à un autre, traité des données hiérarchiques dans une base de données SQL et ont sans doute appris que la gestion de données hiérarchiques n'est pas ce à quoi une base de données relationnelle est destinée. Les tableaux d'une base de données relationnelle ne sont pas hiérarchiques (comme XML), mais sont simplement une liste plate. Les données hiérarchiques ont une relation parent-enfant qui n'est pas naturellement représentée dans une table de base de données relationnelle. | ||
+ | |||
+ | Pour nos besoins, les données hiérarchiques sont une collection de données où chaque élément a un seul parent et zéro ou plusieurs enfants (à l' | ||
+ | |||
+ | {{drawio> | ||
+ | |||
+ | Ces catégories forment une hiérarchie à peu près comme les autres exemples cités ci-dessus. | ||
+ | ===== Modèle de liste adjacente ===== | ||
+ | Généralement, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <file sql adjacency.list.tree.sql> | ||
+ | CREATE TABLE category( | ||
+ | category_id INT AUTO_INCREMENT PRIMARY KEY, | ||
+ | name VARCHAR(20) NOT NULL, | ||
+ | parent INT DEFAULT NULL | ||
+ | ); | ||
+ | |||
+ | INSERT INTO category VALUES | ||
+ | (1, ' | ||
+ | (2, ' | ||
+ | (3, ' | ||
+ | (4, ' | ||
+ | (5, ' | ||
+ | (6, ' | ||
+ | (7, 'MP3 PLAYERS', | ||
+ | (8, ' | ||
+ | (9, 'CD PLAYERS', | ||
+ | (10, '2 WAY RADIOS', | ||
+ | |||
+ | SELECT * | ||
+ | FROM category | ||
+ | ORDER BY category_id; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ category_id ^ name ^ parent ^ | ||
+ | | 1 | ELECTRONICS | ||
+ | | 2 | TELEVISIONS | ||
+ | | 3 | TUBE | ||
+ | | 4 | LCD | 2 | | ||
+ | | 5 | PLASMA | ||
+ | | 6 | PORTABLE ELECTRONICS | 1 | | ||
+ | | 7 | MP3 PLAYERS | ||
+ | | 8 | FLASH | 7 | | ||
+ | | 9 | CD PLAYERS | ||
+ | | 10 | 2 WAY RADIOS | ||
+ | |||
+ | Dans le modèle de liste adjacente, chaque élément du tableau contient un pointeur vers son parent. L' | ||
+ | Le modèle de liste adjacente a l' | ||
+ | Alors que le modèle de liste adjacente peut être traité assez facilement dans le code côté client, travailler avec ce modèle peut être plus problématique en SQL pur. | ||
+ | |||
+ | ==== Récupération d'un arbre complet ==== | ||
+ | La première tâche courante lorsqu' | ||
+ | La façon la plus courante de le faire en SQL pur est d' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT t1.name AS lev1 | ||
+ | , t2.name as lev2 | ||
+ | , t3.name as lev3 | ||
+ | , t4.name as lev4 | ||
+ | FROM category AS t1 | ||
+ | LEFT JOIN category AS t2 ON t2.parent = t1.category_id | ||
+ | LEFT JOIN category AS t3 ON t3.parent = t2.category_id | ||
+ | LEFT JOIN category AS t4 ON t4.parent = t3.category_id | ||
+ | WHERE t1.name = ' | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ lev1 ^ lev2 ^ lev3 ^ lev4 ^ | ||
+ | | ELECTRONICS | TELEVISIONS | ||
+ | | ELECTRONICS | TELEVISIONS | ||
+ | | ELECTRONICS | TELEVISIONS | ||
+ | | ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | ||
+ | | ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS | ||
+ | | ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL | | ||
+ | |||
+ | ==== Trouver tous les nœuds feuille ==== | ||
+ | Nous pouvons trouver tous les nœuds feuilles de notre arbre (ceux qui n'ont pas d' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT t1.name | ||
+ | FROM category AS t1 | ||
+ | LEFT JOIN category as t2 ON t1.category_id = t2.parent | ||
+ | WHERE t2.category_id IS NULL; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | TUBE | | ||
+ | | LCD | | ||
+ | | PLASMA | ||
+ | | FLASH | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | | ||
+ | |||
+ | ==== Retrouver un chemin unique ==== | ||
+ | L' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT t1.name AS lev1 | ||
+ | , t2.name as lev2 | ||
+ | , t3.name as lev3 | ||
+ | , t4.name as lev4 | ||
+ | FROM category AS t1 | ||
+ | LEFT JOIN category AS t2 ON t2.parent = t1.category_id | ||
+ | LEFT JOIN category AS t3 ON t3.parent = t2.category_id | ||
+ | LEFT JOIN category AS t4 ON t4.parent = t3.category_id | ||
+ | WHERE t1.name = ' | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ lev1 ^ lev2 ^ lev3 ^ lev4 ^ | ||
+ | | ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH | | ||
+ | |||
+ | La principale limite d'une telle approche est qu'il faut une auto-jointure pour chaque niveau de la hiérarchie et les performances se dégraderont naturellement avec chaque niveau ajouté à mesure que la jointure deviendra plus complexe. | ||
+ | |||
+ | ==== Limites du modèle de liste adjacente ==== | ||
+ | Travailler avec le modèle de liste adjacente en SQL pur peut être difficile. | ||
+ | Avant de pouvoir voir le chemin complet d'une catégorie, nous devons connaître le niveau auquel elle se trouve. | ||
+ | En outre, il faut être particulièrement attentif lors de la suppression de nœuds en raison du risque de création d' | ||
+ | Certaines de ces limitations peuvent être résolues par l' | ||
+ | Avec un langage procédural, | ||
+ | Nous pouvons également utiliser la programmation procédurale pour supprimer des nœuds sans rendre orphelins des sous-arbres entiers en favorisant un élément enfant et en réordonnant les enfants restants pour qu'ils pointent vers le nouveau parent. | ||
+ | ===== Modèle d' | ||
+ | |||
+ | Dans le modèle d' | ||
+ | Essayez d' | ||
+ | |||
+ | {{drawio> | ||
+ | |||
+ | Remarquez comment notre hiérarchie est toujours maintenue, puisque les catégories de parents enveloppent leurs enfants. | ||
+ | Nous représentons cette forme de hiérarchie dans un tableau en utilisant des valeurs de gauche et de droite pour représenter l' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <file sql nested.set.tree.sql> | ||
+ | CREATE TABLE nested_category ( | ||
+ | category_id INT AUTO_INCREMENT PRIMARY KEY, | ||
+ | name VARCHAR(20) NOT NULL, | ||
+ | lft INT NOT NULL, | ||
+ | rgt INT NOT NULL | ||
+ | ); | ||
+ | |||
+ | INSERT INTO nested_category VALUES | ||
+ | (1, ' | ||
+ | (2, ' | ||
+ | (3, ' | ||
+ | (4, ' | ||
+ | (5, ' | ||
+ | (6, ' | ||
+ | (7, 'MP3 PLAYERS', | ||
+ | (8, ' | ||
+ | (9, 'CD PLAYERS', | ||
+ | (10, '2 WAY RADIOS', | ||
+ | |||
+ | SELECT * | ||
+ | FROM nested_category | ||
+ | ORDER BY category_id; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ category_id ^ name ^ lft ^ rgt ^ | ||
+ | | 1 | ELECTRONICS | ||
+ | | 2 | TELEVISIONS | ||
+ | | 3 | TUBE | ||
+ | | 4 | LCD | 5 | 6 | | ||
+ | | 5 | PLASMA | ||
+ | | 6 | PORTABLE ELECTRONICS | 10 | 19 | | ||
+ | | 7 | MP3 PLAYERS | ||
+ | | 8 | FLASH | 12 | 13 | | ||
+ | | 9 | CD PLAYERS | ||
+ | | 10 | 2 WAY RADIOS | ||
+ | |||
+ | Nous utilisons //lft// et //rgt// parce que //left// et //right// sont des mots réservés à MySQL, voir la [[https:// | ||
+ | |||
+ | Alors comment déterminer les valeurs de gauche et de droite ? | ||
+ | Nous commençons à numéroter du côté le plus à gauche du nœud extérieur et nous continuons vers la droite : | ||
+ | |||
+ | {{drawio> | ||
+ | |||
+ | Cette conception peut également être appliquée à un arbre classique : | ||
+ | |||
+ | {{drawio> | ||
+ | |||
+ | Lorsque l'on travaille avec un arbre, on travaille de gauche à droite, une couche à la fois, en descendant vers les enfants de chaque nœud avant d' | ||
+ | |||
+ | ==== Récupération d'un arbre complet ==== | ||
+ | Nous pouvons récupérer l' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT node.name | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND parent.name = ' | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | TELEVISIONS | ||
+ | | TUBE | | ||
+ | | LCD | | ||
+ | | PLASMA | ||
+ | | PORTABLE ELECTRONICS | | ||
+ | | MP3 PLAYERS | ||
+ | | FLASH | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | ||
+ | |||
+ | Contrairement à nos précédents exemples avec le modèle de liste adjacente, cette requête fonctionnera quelle que soit la profondeur de l' | ||
+ | Nous ne nous préoccupons pas de la valeur //rgt// du nœud dans notre clause __BETWEEN__, | ||
+ | ==== Trouver tous les nœuds feuille ==== | ||
+ | Trouver tous les nœuds de feuilles dans le modèle d' | ||
+ | Si vous regardez le tableau, vous pouvez remarquer que les valeurs //lft// et //rgt// pour les nœuds de feuilles sont des nombres consécutifs. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT name | ||
+ | FROM nested_category | ||
+ | WHERE rgt = lft + 1; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | TUBE | | ||
+ | | LCD | | ||
+ | | PLASMA | ||
+ | | FLASH | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | | ||
+ | |||
+ | ==== Retrouver un chemin unique ==== | ||
+ | |||
+ | Avec le modèle d' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT parent.name | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.name = ' | ||
+ | ORDER BY parent.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | PORTABLE ELECTRONICS | | ||
+ | | MP3 PLAYERS | ||
+ | | FLASH | | ||
+ | |||
+ | ==== Trouver la profondeur des nœuds ==== | ||
+ | |||
+ | Nous avons déjà examiné comment montrer l' | ||
+ | Cela peut être fait en ajoutant une fonction __COUNT__ et une clause __GROUP BY__ à notre requête existante pour afficher l' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT node.name | ||
+ | , (COUNT(parent.name) - 1) AS depth | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ depth ^ | ||
+ | | ELECTRONICS | ||
+ | | TELEVISIONS | ||
+ | | TUBE | ||
+ | | LCD | 2 | | ||
+ | | PLASMA | ||
+ | | PORTABLE ELECTRONICS | 1 | | ||
+ | | MP3 PLAYERS | ||
+ | | FLASH | 3 | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | ||
+ | |||
+ | Nous pouvons utiliser la valeur de profondeur pour indenter nos noms de catégorie avec les fonctions de chaîne __CONCAT__ et __REPEAT__ : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT(' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……MP3 PLAYERS | ||
+ | | ………FLASH | ||
+ | | ……CD PLAYERS | ||
+ | | ……2 WAY RADIOS | ||
+ | |||
+ | Bien entendu, dans une application cliente, vous serez plus enclin à utiliser directement la valeur de profondeur pour afficher votre hiérarchie. | ||
+ | Les développeurs web pourraient faire une boucle dans l' | ||
+ | ==== Trouver la profondeur d'un sous-arbre ==== | ||
+ | |||
+ | Lorsque nous avons besoin d' | ||
+ | Au lieu de cela, nous ajoutons une troisième auto-jointure, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT node.name | ||
+ | , (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent, | ||
+ | nested_category AS sub_parent, | ||
+ | ( | ||
+ | SELECT node.name | ||
+ | , (COUNT(parent.name) - 1) AS depth | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.name = ' | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft | ||
+ | ) AS sub_tree | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt | ||
+ | AND sub_parent.name = sub_tree.name | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ depth ^ | ||
+ | | PORTABLE ELECTRONICS | 0 | | ||
+ | | MP3 PLAYERS | ||
+ | | FLASH | 2 | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | ||
+ | |||
+ | Cette fonction peut être utilisée avec n' | ||
+ | Les valeurs de profondeur sont toujours relatives au nœud nommé. | ||
+ | ==== Trouver les enfants immédiats d'un nœud ==== | ||
+ | |||
+ | Imaginez que vous présentez une catégorie de produits électroniques sur le site web d'un détaillant. | ||
+ | Lorsqu' | ||
+ | Pour cela, nous devons afficher le nœud et ses sous-nœuds immédiats uniquement. | ||
+ | Par exemple, lorsque nous affichons la catégorie // | ||
+ | |||
+ | Cela peut être facilement réalisé en ajoutant une clause __HAVING__ à notre requête précédente : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT node.name | ||
+ | , (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent, | ||
+ | nested_category AS sub_parent, | ||
+ | ( | ||
+ | SELECT node.name | ||
+ | , (COUNT(parent.name) - 1) AS depth | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.name = ' | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft | ||
+ | ) AS sub_tree | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt | ||
+ | AND sub_parent.name = sub_tree.name | ||
+ | GROUP BY node.name | ||
+ | HAVING depth <= 1 | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ depth ^ | ||
+ | | PORTABLE ELECTRONICS | ||
+ | | MP3 PLAYERS | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | ||
+ | |||
+ | Si vous ne souhaitez pas afficher le nœud parent, changez la ligne '' | ||
+ | ==== Fonctions d’agrégation dans un ensemble imbriqué ==== | ||
+ | |||
+ | Ajoutons une table de produits que nous pouvons utiliser avec des fonctions agrégées : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | CREATE TABLE product ( | ||
+ | product_id INT AUTO_INCREMENT PRIMARY KEY, | ||
+ | name VARCHAR(40), | ||
+ | category_id INT NOT NULL | ||
+ | ); | ||
+ | |||
+ | INSERT INTO product (name, category_id) VALUES | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | (' | ||
+ | ('CD To go!', 9), | ||
+ | (' | ||
+ | |||
+ | SELECT * | ||
+ | FROM product; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ product_id | ||
+ | | 1 | 20" TV | 3 | | ||
+ | | 2 | 36" TV | 3 | | ||
+ | | 3 | Super-LCD 42" | ||
+ | | 4 | Ultra-Plasma 62" | ||
+ | | 5 | Value Plasma 38" | ||
+ | | 6 | Power-MP3 128mb | 7 | | ||
+ | | 7 | Super-Shuffle 1gb | 8 | | ||
+ | | 8 | Porta CD | 9 | | ||
+ | | 9 | CD To go! | 9 | | ||
+ | | 10 | Family Talk 360 | 10 | | ||
+ | |||
+ | Produisons maintenant une requête qui récupère notre arbre de catégories, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT parent.name | ||
+ | , COUNT(product.name) | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent, | ||
+ | product | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.category_id = product.category_id | ||
+ | GROUP BY parent.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ COUNT(product.name) ^ | ||
+ | | ELECTRONICS | ||
+ | | TELEVISIONS | ||
+ | | TUBE | ||
+ | | LCD | 1 | | ||
+ | | PLASMA | ||
+ | | PORTABLE ELECTRONICS | 5 | | ||
+ | | MP3 PLAYERS | ||
+ | | FLASH | 1 | | ||
+ | | CD PLAYERS | ||
+ | | 2 WAY RADIOS | ||
+ | |||
+ | C'est notre requête classique de récupération d' | ||
+ | Comme vous pouvez le voir, il y a un nombre pour chaque catégorie et le nombre de sous-catégories est reflété dans les catégories parentes. | ||
+ | ==== Ajout d'un nœud ==== | ||
+ | |||
+ | Maintenant que nous avons appris à interroger notre arbre, nous devrions examiner comment le mettre à jour en y ajoutant un nouveau nœud. | ||
+ | Examinons à nouveau notre diagramme d' | ||
+ | |||
+ | {{drawio> | ||
+ | |||
+ | Si nous voulions ajouter un nouveau nœud entre les nœuds // | ||
+ | Nous ajouterions alors le nouveau nœud avec les valeurs //lft// et //rgt// appropriées. | ||
+ | Bien que cela puisse être fait avec une procédure stockée dans MySQL 5, < | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | -- Verrouillage en écriture de la table | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | -- Récupération des informations de l' | ||
+ | SELECT @myRight := rgt | ||
+ | FROM nested_category | ||
+ | WHERE name = ' | ||
+ | |||
+ | -- Mise à jour des bornes droites des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt + 2 | ||
+ | WHERE rgt > @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes gauches des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET lft = lft + 2 | ||
+ | WHERE lft > @myRight; | ||
+ | |||
+ | -- Insertion du nouvel élément | ||
+ | INSERT INTO nested_category (name, lft, rgt) VALUES | ||
+ | ('GAME CONSOLES', | ||
+ | |||
+ | -- Suppression des verrous | ||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Nous pouvons alors vérifier notre arborescence grâce à notre requête d' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT( ' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …GAME CONSOLES | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……MP3 PLAYERS | ||
+ | | ………FLASH | ||
+ | | ……CD PLAYERS | ||
+ | | ……2 WAY RADIOS | ||
+ | |||
+ | Si nous voulons plutôt ajouter un nœud en tant qu' | ||
+ | Ajoutons un nouveau nœud //FRS// sous le nœud //2 WAY RADIOS// : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | SELECT @myLeft := lft | ||
+ | FROM nested_category | ||
+ | |||
+ | WHERE name = '2 WAY RADIOS'; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt + 2 WHERE rgt > @myLeft; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET lft = lft + 2 WHERE lft > @myLeft; | ||
+ | |||
+ | INSERT INTO nested_category(name, | ||
+ | (' | ||
+ | |||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Dans cet exemple, nous développons tout à droite du numéro de gauche de notre nouveau nœud parent, puis nous plaçons le nœud à droite de la valeur de gauche. | ||
+ | Comme vous pouvez le voir, notre nouveau nœud est maintenant correctement imbriqué : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT( ' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …GAME CONSOLES | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……MP3 PLAYERS | ||
+ | | ………FLASH | ||
+ | | ……CD PLAYERS | ||
+ | | ……2 WAY RADIOS | ||
+ | | ………FRS | ||
+ | ==== Suppression d'un nœud ==== | ||
+ | |||
+ | La dernière tâche de base impliquée dans le travail avec des ensembles imbriqués est la suppression des nœuds. | ||
+ | La marche à suivre pour supprimer un nœud dépend de la position du nœud dans la hiérarchie ; il est plus facile de supprimer des nœuds feuilles que des nœuds enfants, car nous devons nous occuper des nœuds orphelins. | ||
+ | |||
+ | Lors de la suppression d'un nœud de feuille, le processus est tout à fait inverse à celui de l' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | -- Verrouillage en écriture de la table | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | -- Récupération des informations de l' | ||
+ | SELECT @myLeft := lft | ||
+ | , @myRight := rgt | ||
+ | , @myWidth := rgt - lft + 1 | ||
+ | FROM nested_category | ||
+ | WHERE name = 'GAME CONSOLES'; | ||
+ | |||
+ | -- Suppression de l' | ||
+ | DELETE FROM nested_category | ||
+ | WHERE lft BETWEEN @myLeft AND @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes droites des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - @myWidth | ||
+ | WHERE rgt > @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes gauches des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET lft = lft - @myWidth | ||
+ | WHERE lft > @myRight; | ||
+ | |||
+ | -- Suppression des verrous | ||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Et une fois de plus, nous exécutons notre requête d' | ||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT( ' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……MP3 PLAYERS | ||
+ | | ………FLASH | ||
+ | | ……CD PLAYERS | ||
+ | | ……2 WAY RADIOS | ||
+ | | ………FRS | ||
+ | |||
+ | Cette approche fonctionne tout aussi bien pour supprimer un nœud et tous ses enfants : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | SELECT @myLeft := lft | ||
+ | , @myRight := rgt | ||
+ | , @myWidth := rgt - lft + 1 | ||
+ | FROM nested_category | ||
+ | WHERE name = 'MP3 PLAYERS'; | ||
+ | |||
+ | DELETE FROM nested_category | ||
+ | WHERE lft BETWEEN @myLeft AND @myRight; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - @myWidth | ||
+ | WHERE rgt > @myRight; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET lft = lft - @myWidth | ||
+ | WHERE lft > @myRight; | ||
+ | |||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Et une fois de plus, nous exécutons notre requête pour valider que nous avons réussi à supprimer un sous-arbre entier : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT( ' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……CD PLAYERS | ||
+ | | ……2 WAY RADIOS | ||
+ | | ………FRS | ||
+ | |||
+ | L' | ||
+ | Dans certains cas, il est possible de changer le nom en place jusqu' | ||
+ | Dans d' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | SELECT @myLeft := lft | ||
+ | , @myRight := rgt | ||
+ | , @myWidth := rgt - lft + 1 | ||
+ | FROM nested_category | ||
+ | WHERE name = ' | ||
+ | |||
+ | DELETE FROM nested_category | ||
+ | WHERE lft = @myLeft; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - 1 | ||
+ | , lft = lft - 1 | ||
+ | WHERE lft BETWEEN @myLeft AND @myRight; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - 2 | ||
+ | WHERE rgt > @myRight; | ||
+ | |||
+ | UPDATE nested_category | ||
+ | SET lft = lft - 2 | ||
+ | WHERE lft > @myRight; | ||
+ | |||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Dans ce cas, nous en soustrayons deux de tous les éléments à droite du nœud (puisque sans les enfants, il aurait une largeur de deux), et un des nœuds qui sont ses enfants (pour combler l' | ||
+ | Une fois de plus, nous pouvons confirmer que nos éléments ont été promus : | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT CONCAT( REPEAT( ' | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | GROUP BY node.name | ||
+ | ORDER BY node.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …CD PLAYERS | ||
+ | | …2 WAY RADIOS | | ||
+ | | ……FRS | ||
+ | |||
+ | D' | ||
+ | ===== Autres ressources ===== | ||
+ | |||
+ | * [[https:// | ||
+ | * [[https:// | ||
+ | |||
+ | ====== Manipulations de données supplémentaires ====== | ||
+ | |||
+ | <WRAP info> | ||
+ | Ces manipulations ne sont pas abordées dans l' | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Retrouver un chemin unique sans le nœud ===== | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT parent.name | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.name = ' | ||
+ | AND node.category_id <> parent.category_id | ||
+ | ORDER BY parent.lft; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | PORTABLE ELECTRONICS | | ||
+ | | MP3 PLAYERS | ||
+ | |||
+ | ===== Retrouver le parent immédiat d'un nœud ===== | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | SELECT parent.name | ||
+ | FROM nested_category AS node, | ||
+ | nested_category AS parent | ||
+ | WHERE node.lft BETWEEN parent.lft AND parent.rgt | ||
+ | AND node.name = ' | ||
+ | AND node.category_id <> parent.category_id | ||
+ | ORDER BY parent.lft DESC | ||
+ | LIMIT 1; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | MP3 PLAYERS | | ||
+ | |||
+ | ===== Déplacement d'un nœud ===== | ||
+ | |||
+ | Ici, on ne couvre que le cas du déplacement d'un nœud vers un nœud qui n'est pas un descendant du nœud traité. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | -- Verrouillage en écriture de la table | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | -- Récupération des informations de l' | ||
+ | SELECT @myLeft := lft | ||
+ | , @myRight := rgt | ||
+ | , @myWidth := rgt - lft + 1 | ||
+ | FROM nested_category | ||
+ | WHERE name = 'CD PLAYERS'; | ||
+ | |||
+ | -- Récupération des informations du nouvel élément parent | ||
+ | SELECT @myNewParentLeft := lft | ||
+ | , @myNewParentRight := rgt | ||
+ | FROM nested_category | ||
+ | WHERE name = ' | ||
+ | |||
+ | -- Calcul de la valeur de déplacement de l' | ||
+ | SELECT @offsetWidth := @myNewParentRight - @myRight - 1 + IF( SIGN( @myNewParentRight - @myRight ) < 1 , @myWidth , 0 ); | ||
+ | |||
+ | -- Calcul de la borne gauche de déplacement des autres éléments | ||
+ | SELECT @offsetLeft := IF( SIGN( @offsetWidth ) = 1 , @myRight , @myNewParentRight ); | ||
+ | |||
+ | -- Calcul de la borne droite de déplacement des autres éléments | ||
+ | SELECT @offsetRight := IF( SIGN( @offsetWidth ) = 1 , @myNewParentRight - 1 , @myLeft - 1 ); | ||
+ | |||
+ | -- Calcul de la valeur de déplacement des autres éléments | ||
+ | SELECT @signedNodeWidth := SIGN( @offsetWidth ) * @myWidth; | ||
+ | |||
+ | -- Exclusion temporaire de l' | ||
+ | UPDATE nested_category | ||
+ | SET lft = -lft | ||
+ | , rgt = -rgt | ||
+ | WHERE lft >= @myLeft | ||
+ | AND rgt <= @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes droites des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - @signedNodeWidth | ||
+ | WHERE rgt BETWEEN @offsetLeft AND @offsetRight; | ||
+ | |||
+ | -- Mise à jour des bornes gauches des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET lft = lft - @signedNodeWidth | ||
+ | WHERE lft BETWEEN @offsetLeft AND @offsetRight; | ||
+ | |||
+ | -- Repositionnement de l' | ||
+ | UPDATE nested_category | ||
+ | SET lft = -lft + @offsetWidth | ||
+ | , rgt = -rgt + @offsetWidth | ||
+ | WHERE rgt < 0; | ||
+ | |||
+ | -- Suppression des verrous | ||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …GAME CONSOLES | ||
+ | | …PORTABLE ELECTRONICS | | ||
+ | | ……MP3 PLAYERS | ||
+ | | ………FLASH | ||
+ | | ……2 WAY RADIOS | ||
+ | | ………FRS | ||
+ | | …CD PLAYERS | ||
+ | ===== Transformation d'un nœud en nœud racine ===== | ||
+ | |||
+ | Cela implique de déplacer un nœud ainsi que l' | ||
+ | Le nœud sélectionné pour être un nouveau nœud racine n' | ||
+ | À la fin de la transformation, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code sql> | ||
+ | -- Verrouillage en écriture de la table | ||
+ | LOCK TABLE nested_category WRITE; | ||
+ | |||
+ | -- Récupération des informations de l' | ||
+ | SELECT @myLeft := lft | ||
+ | , @myRight := rgt | ||
+ | , @myWidth := rgt - lft + 1 | ||
+ | FROM nested_category | ||
+ | WHERE name = ' | ||
+ | |||
+ | -- Récupération des informations de déplacement de l' | ||
+ | SELECT @offsetWidth := MAX(rgt) - @myRight | ||
+ | FROM nested_category; | ||
+ | |||
+ | -- Exclusion temporaire de l' | ||
+ | UPDATE nested_category | ||
+ | SET lft = -lft | ||
+ | , rgt = -rgt | ||
+ | WHERE lft >= @myLeft | ||
+ | AND rgt <= @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes droites des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET rgt = rgt - @myWidth | ||
+ | WHERE rgt >= @myRight; | ||
+ | |||
+ | -- Mise à jour des bornes gauches des éléments concernés | ||
+ | UPDATE nested_category | ||
+ | SET lft = lft - @myWidth | ||
+ | WHERE lft >= @myRight; | ||
+ | |||
+ | -- Repositionnement de l' | ||
+ | UPDATE nested_category | ||
+ | SET lft = -lft + @offsetWidth | ||
+ | , rgt = -rgt + @offsetWidth | ||
+ | WHERE rgt < 0; | ||
+ | |||
+ | -- Suppression des verrous | ||
+ | UNLOCK TABLES; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ^ name ^ | ||
+ | | ELECTRONICS | ||
+ | | …TELEVISIONS | ||
+ | | ……TUBE | ||
+ | | ……LCD | ||
+ | | ……PLASMA | ||
+ | | …GAME CONSOLES | ||
+ | | PORTABLE ELECTRONICS | | ||
+ | | …MP3 PLAYERS | ||
+ | | ……FLASH | ||
+ | | …CD PLAYERS | ||
+ | | …2 WAY RADIOS | ||
+ | | ……FRS |