2009-08-22 2 views
4

Dies ist meine Tabelle:Wie kann ich alle Geschwister zu meinem Knoten und seinen Vorfahren in einem hierarchischen Kategoriebaum finden?

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; 

ich eine URL, die wie folgt aussieht: products.php?category=5

Aus der Kategorie-ID ich auf jeder Ebene der Hierarchie alle Kategorien mit den gleichen Eltern abrufen müssen. Ich habe einen Weg gefunden, dies zu tun, aber ich denke, es ist ineffizient, gibt es einen besseren Weg, dies zu tun?

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 

Zum Beispiel, sagen wir mal meine Kategoriebaum sieht wie folgt aus:

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

Lassen Sie uns sagen, dass der Benutzer „CD-Player“ ausgewählt hat, möchte ich das abrufen folgende Information:

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 

Ich möchte alle ausgewählten Kategorien abrufen a nd alle Kategorien, die auf derselben Ebene wie die ausgewählten Kategorien sind, zusammen mit Tiefeninformationen und welche Kategorien ausgewählt sind.

Der Grund, warum ich dies tun muss, ist, dass die Produktseite ein Dropdown-Navigationsmenü für jede Kategorie bis zur ausgewählten Kategorie enthalten kann.

Antwort

1

Ich bin mir nicht sicher, ob ich alles, was folgen, aber es klingt wie Sie alle unmittelbaren Kinder der Kategorie wollen 5.

Hier ist ein Weg, dies zu tun:

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 = ?; 

bearbeiten : Okay, ich verstehe jetzt, dass die obige Lösung nur ein Teil dessen ist, was Sie wollen. Sie wollen:

  • direkte Vorfahren der CD-Player
  • "Uncles" von CD-Playern (Geschwister von Ahnen)
  • Geschwister von CD-Player
  • Kinder CD-Player

Let me arbeite für ein paar Minuten daran.


Hier ist, was ich habe kommen mit:

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 ist CD-Player
  • selected ist Vorfahren der CD-Player (Elektronik, tragbare elektronische Geräte, CD-Spieler)
  • descendant ist irgendein Kind oder Enkel usw.jedes selected Vorfahr
  • intermediate ist ein Abkömmling eines jeden selected Vorfahren, die auch ein Elternteil descendant - es keine von diesen sein muss, damit die IS NULL Beschränkung.
  • c ist die Ahnenkette von descendant zurück nach oben, um die Tiefe zu bestimmen.

Ich habe erkannt, dass meine Lösung auch alle Nachkommen des current Knoten zurückkehren würde. Wenn Sie also "portable electronics" anzeigen würden, würde die Abfrage ihre untergeordneten Elemente zurückgeben, aber es würde auch das Enkelkind "flash" zurückgeben, was möglicherweise nicht das ist, was Sie wollen.

+0

Vielen Dank, das ist, was ich brauchte. –

+0

Die Tiefeninformationen schienen falsch zu sein, bis ich DISTINCT hinzugefügt: COUNT (DISTINCT c.CategoryId) AS Tiefe –

+0

Danke, ich habe die Abfrage nicht getestet. Ich habe diesen Fehler behoben. –

1

Für die vorliegende Aufgabe ist das geschachtelte Mengenmodell nahezu nutzlos. Mein Vorschlag ist, dass Sie die Tabelle vorverarbeiten, um eine materialisierte Pfadspalte hinzuzufügen, und dann diese Spalte verwenden, um die Frage zu beantworten. Diese beiden Schritte benötigen wahrscheinlich weniger Arbeit als jede Lösung, die auf der Darstellung der verschachtelten Menge beruht.

Das Finden des materialisierten Pfads ist ein relativ "bekanntes" Problem, aber Ihre spezifische Frage ist nicht, also hier ist eine Abfrage basierend auf Pfaden, die es beantwortet.

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 ist (SELECT MyPath FROM Kategorien WHERE ID =: CategorySelected)), die Sie in die Abfrage precompute oder integrieren können. Darüber hinaus schützt das ABS vor einem Abfrageplan, in dem der SUBSTRING auch dann berechnet wird, wenn MyPath = '' und daher SUBSTRING mit einem negativen Längenparameter aufrufen.

Hier ist eine vollständige Repro in SQL Server-Syntax für die spezifischen Daten, die Sie in Ihrer Frage verwendet haben. Meine Abfrage verwendet nicht die Spalten lft und rgt, also habe ich sie nicht eingeschlossen, aber es gibt keinen Grund, warum Sie diese in Ihrer Tabelle nicht haben können, wenn sie anderen Zwecken dienen.

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;