2009-08-22 6 views
4

Ceci est ma table:Comment puis-je trouver tous les frères et soeurs de mon nœud et de ses ancêtres dans une arborescence de catégories hiérarchique?

CREATE TABLE IF NOT EXISTS `Category` (
`Name` varchar(25) NOT NULL, 
`lft` INT UNSIGNED NOT NULL, 
`rgt` INT UNSIGNED NOT NULL, 
`CategoryId` int UNSIGNED auto_increment NOT NULL, 
PRIMARY KEY (`CategoryId`) 
) Engine = InnoDb; 

J'ai une URL qui ressemble à ceci: products.php?category=5

de l'ID de la catégorie que je dois récupérer toutes les catégories avec le même parent à chaque niveau de la hiérarchie. J'ai trouvé un moyen de le faire mais je pense que c'est inefficace, y a-t-il une meilleure façon de le faire?

CREATE VIEW category_tree AS 
SELECT node.name as name, 
    node.categoryId as categoryId, 
    node.lft as lft, 
    node.rgt as rgt, 
    (COUNT(parent.categoryId) - 1) AS depth 
FROM Category AS node, 
Category AS parent 
WHERE node.lft BETWEEN parent.lft AND parent.rgt 
GROUP BY node.categoryId; 

SELECT tree.name, tree.depth, tree.categoryId, 
(node.lft BETWEEN tree.lft AND tree.rgt) AS is_selected 
FROM category_tree as tree 
JOIN category_tree AS node ON node.categoryId = :selectedCategory 
JOIN (
    SELECT MAX(tree.lft) as lft 
    FROM category_tree as tree 
    JOIN category_tree AS node ON node.categoryId = :selectedCategory 
    WHERE 
    tree.depth = node.depth -1 
    AND tree.lft < node.lft 
) AS parent_finder 
LEFT JOIN category_tree AS parent ON parent.lft = parent_finder.lft 
WHERE tree.depth < node.depth 
OR (
    tree.depth = node.depth 
    AND tree.lft BETWEEN parent.lft AND parent.rgt 
) 
OR (
    tree.lft BETWEEN node.lft AND node.rgt 
    AND tree.depth <= node.depth + 1 
) 
ORDER BY tree.depth, tree.name 

Par exemple, disons que mon arbre de catégorie ressemble à ceci:

http://dev.mysql.com/tech-resources/articles/hierarchical-data-1.png
(de Managing Hierarchical Data in MySQL)

Disons que l'utilisateur a sélectionné « lecteurs cd », je veux récupérer le Information suivante:

name     depth is_selected 
electronics   0   1 
portable electronics 1   1 
televisions   1   0 
mp3 players   2   0 
cd players   2   1 
2 way radios   2   0 

Je veux récupérer toutes les catégories sélectionnées Toutes les catégories qui se trouvent au même niveau que les catégories sélectionnées ainsi que les informations de profondeur et les catégories sélectionnées.

La raison pour laquelle je dois faire ceci est que la page des produits puisse inclure un menu de navigation déroulant pour chaque niveau de catégorie jusqu'à la catégorie sélectionnée.

Répondre

1

Je ne suis pas sûr que je suis tout cela, mais il semble que vous voulez que tous les enfants immédiats de la catégorie 5.

Voici une façon de le faire:

SELECT child.* 
FROM Category parent 
    JOIN Category child 
    ON (child.lft BETWEEN parent.lft AND parent.rgt) 
    LEFT JOIN Category intermediate 
    ON (intermediate.lft > parent.lft AND intermediate.rgt < parent.rgt 
     AND child.lft > intermediate.lft AND child.rgt < intermediate.rgt) 
WHERE intermediate.CategoryId IS NULL 
    AND parent.CategoryId = ?; 

modifier : D'accord, je comprends maintenant que la solution ci-dessus n'est qu'une partie de ce que vous voulez. Vous voulez:

  • ancêtres directs des joueurs CD
  • "Oncles" des lecteurs de CD (fratrie des ancêtres)
  • frères et soeurs des lecteurs de CD
  • Enfants de lecteurs de CD

Permettez-moi travailler dessus pendant quelques minutes.


Voici ce que je suis venu avec:

SELECT descendant.*, 
    (current.lft BETWEEN descendant.lft AND descendant.rgt) AS is_selected, 
    COUNT(DISTINCT c.CategoryId) AS depth 
FROM Category current 
JOIN Category selected 
    ON (current.lft BETWEEN selected.lft AND selected.rgt) 
JOIN Category descendant 
    ON (descendant.lft BETWEEN selected.lft AND selected.rgt) 
LEFT JOIN Category intermediate 
    ON (intermediate.lft > selected.lft AND intermediate.rgt < selected.rgt 
    AND descendant.lft > intermediate.lft AND descendant.lft < intermediate.rgt) 
JOIN Category c 
    ON (descendant.lft BETWEEN c.lft AND c.rgt) 
WHERE intermediate.CategoryId IS NULL 
    AND current.CategoryId = ? 
GROUP BY descendant.CategoryId 
ORDER BY depth, descendant.name; 
  • current est un lecteur de CD
  • selected est-ancêtres des lecteurs de CD (électronique, appareils électroniques portables, lecteurs de CD)
  • descendant est un enfant ou un petit-enfant, etc.de chaque ancêtre selected
  • intermediate est un descendant de chaque ancêtre selected qui est aussi un parent de descendant - il doit y avoir aucun de ceux-ci, d'où la restriction IS NULL.
  • c est la chaîne des ancêtres de descendant de retour vers le haut, afin de déterminer la profondeur.

Je viens de réaliser que ma solution reviendrait également tous les descendants du nœud current. Donc, si vous étiez en train de regarder "électronique portable", la requête retournerait ses enfants, mais elle retournerait aussi le petit-fils "flash" qui peut ne pas être ce que vous voulez.

+0

Merci, c'est ce dont j'avais besoin. –

+0

Les informations de profondeur semblaient être incorrectes jusqu'à ce que j'ajoute DISTINCT: COUNT (DISTINCT c.CategoryId) AS profondeur –

+0

Merci, je n'ai pas testé la requête. J'ai modifié pour inclure ce correctif. –

1

Pour la tâche en cours, le modèle de jeu imbriqué est presque inutile. Ma suggestion est que vous prétraitez le tableau pour ajouter une colonne de chemin matérialisé, puis utilisez cette colonne pour répondre à la question. Ces deux étapes ensemble sont susceptibles de prendre moins de travail que toute solution qui repose sur la représentation de l'ensemble imbriqué.

Trouver le chemin matérialisé est un problème relativement «connu», mais votre question spécifique ne l'est pas, alors voici une requête basée sur les chemins qui y répond.

SELECT 
    Name, 
    len(MyPath)/4 AS Depth, 
    CASE WHEN :PathSelected LIKE MyPath + '%' THEN 1 ELSE 0 END AS Selected 
FROM Category 
WHERE MyPath = '' 
OR (
    MyPath <> '' 
    AND MyPath LIKE SUBSTRING(:PathSelected,1,ABS(LEN(MyPath) - 4)) + '____' 
); 

: PathSelected est (SELECT MyPath des catégories WHERE ID =: CategorySelected)), que vous pouvez précalculer ou les incorporer dans la requête. De plus, l'ABS protège contre un plan de requête où SUBSTRING est calculé même lorsque MyPath = '' et appelle donc SUBSTRING avec un paramètre de longueur négatif.

Voici une complète repro dans la syntaxe SQL Server pour les données spécifiques que vous avez utilisées dans votre question. Ma requête n'utilise pas les colonnes lft et rgt, donc je ne les ai pas inclus, mais il n'y a aucune raison que vous ne puissiez pas les avoir dans votre table s'ils servent d'autres utilisations.

create table Category(
    Name varchar(25) NOT NULL, 
    ID int NOT NULL PRIMARY KEY, 
    MyPath varchar(200) NOT NULL 
); 
GO 

insert into Category values 
    ('ELECTRONICS',1,''), 
    ('TELEVISIONS',2,'/001'), 
    ('PORTABLE ELECTRONICS',3,'/002'), 
    ('FLASH',4,'/002/001/001'), 
    ('MP3 PLAYERS',5,'/002/001'), 
    ('2 WAY RADIOS',6,'/002/002'), 
    ('CD PLAYERS',7,'/002/003'), 
    ('PLASMA',8,'/001/003'), 
    ('LCD',9,'/001/002'), 
    ('TUBE',10,'/001/001'); 
GO 

DECLARE @Selected int = 7; 
DECLARE @MyPath varchar(200) = (
    SELECT MyPath FROM Category 
    WHERE ID = @Selected 
); 

SELECT 
    Name, 
    len(MyPath)/4 AS Depth, 
    CASE WHEN @MyPath LIKE MyPath + '%' THEN 1 ELSE 0 END AS Selected 
FROM Category 
WHERE MyPath = '' 
OR (
    MyPath <> '' 
    AND MyPath LIKE SUBSTRING(@MyPath,1,LEN(MyPath) - 4) + '____' 
); 
GO 

DROP TABLE Category; 
Questions connexes