2013-02-15 68 views
15

什麼是排序像這樣的表的最佳方法:爲了SQL樹層次

CREATE TABLE category(
    id INT(10), 
    parent_id INT(10), 
    name VARCHAR(50) 
); 

INSERT INTO category (id, parent_id, name) VALUES 
(1, 0, 'pizza'),  --node 1 
(2, 0, 'burger'),  --node 2 
(3, 0, 'coffee'),  --node 3 
(4, 1, 'piperoni'),  --node 1.1 
(5, 1, 'cheese'),  --node 1.2 
(6, 1, 'vegetariana'), --node 1.3 
(7, 5, 'extra cheese'); --node 1.2.1 

要通過ID名稱排序分級它
'比薩' //節點1
'piperoni' //節點1.1
'奶酪' //節點1.2
'額外的奶酪' //節點1.2.1
'vegetariana' //節點1.3
「漢堡」 //節點2
「咖啡」 //節點3

編輯:名稱的端部的數量是形象化strucutre更好,這是不進行排序。

編輯2:如多次提到...在的name「奶酪1.2」末尾的數字只是爲了可視化目的,而不是爲排序。我將他們作爲評論移動,太多人感到困惑,抱歉。

+2

Oracle有一種方法可以通過'START WITH parent_id = 0 CONNECT BY PRIOR id = parent_id ORDER SIBLINGS BY id ASC'來完成。我認爲MySQL沒有這樣的分層查詢。 – Benoit 2013-02-15 07:53:40

+0

@Benoit:實際上幾乎所有的DBMS *除少數(包括MySQL)*之外都可以使用遞歸公用表表達式來做類似的事情。 – 2013-02-15 09:01:25

+1

是已經定義的tabel結構還是您正在規劃階段並可以選擇其他結構?你打算在桌上有多少個參賽作品?它經常被修改,還是對它有許多讀取權限很重要? – 2013-02-19 08:34:19

回答

10

通過添加路徑列和觸發器,可以很容易地完成此操作。

首先添加varchar列將包含從根到節點的路徑:

ALTER TABLE category ADD path VARCHAR(50) NULL; 

然後,添加其計算在插入路徑上的觸發:

(簡單地concats與路徑的新的id

:父)通過路徑

CREATE TRIGGER set_path BEFORE INSERT ON category 
    FOR EACH ROW SET NEW.path = 
    CONCAT(IFNULL((select path from category where id = NEW.parent_id), '0'), '.', New.id); 

然後,只需選擇順序的

結果:

pizza   0.1 
piperoni  0.1.4 
cheese  0.1.5 
extra cheese 0.1.5.7 
vegetariana 0.1.6 
burger  0.2 
coffee  0.3 

fiddle

這種方式的維護成本也很小。插入時隱藏路徑字段,並通過觸發器進行計算。刪除節點沒有開銷,因爲節點的所有子節點也都被刪除。唯一的問題是更新節點的parent_id;那麼,不要這樣做! :)

+1

謝謝你的建設性答案。 其優雅的解決方案,無需額外的代碼和支持。 – 2013-02-26 10:45:20

+0

從來沒有想過這 - 非常優雅,非常有用。 – Sherlock 2016-03-23 12:51:49

0

在您的SQL查詢結束時嘗試ORDER BY name , id

這將按名稱排序並使用id來解決任何關係。

+1

是真的..但名稱是例子..更好地更好地形象化結構。 – 2013-02-15 07:52:26

1
SELECT * FROM category ORDER BY name, parent_id ASC 
9

如果只有3層的嵌套,如果你有多個嵌套層次會比較棘手

你可以寫更多的嵌套級別,你可以做這樣的事情

SELECT c1.name FROM category as c1 LEFT JOIN category as c2 
    ON c1.parent_id = c2.id OR (c1.parent_id = 0 AND c1.id = c2.id) 
    ORDER BY c2.parent_id, c2.id, c1.id; 

的功能

delimiter ~ 
DROP FUNCTION getPriority~ 

CREATE FUNCTION getPriority (inID INT) RETURNS VARCHAR(255) DETERMINISTIC 
begin 
    DECLARE gParentID INT DEFAULT 0; 
    DECLARE gPriority VARCHAR(255) DEFAULT ''; 
    SET gPriority = inID; 
    SELECT parent_id INTO gParentID FROM category WHERE ID = inID; 
    WHILE gParentID > 0 DO 
    SET gPriority = CONCAT(gParentID, '.', gPriority); 
    SELECT parent_id INTO gParentID FROM category WHERE ID = gParentID; 
    END WHILE; 
    RETURN gPriority; 
end~ 

delimiter ; 

,所以我現在

SELECT * FROM category ORDER BY getPriority(ID); 

我有

+------+-----------+--------------------+ 
| ID | parent_id | name    | 
+------+-----------+--------------------+ 
| 1 |   0 | pizza 1   | 
| 4 |   1 | piperoni 1.1  | 
| 5 |   1 | cheese 1.2   | 
| 7 |   5 | extra cheese 1.2.1 | 
| 6 |   1 | vegetariana 1.3 | 
| 2 |   0 | burger 2   | 
| 3 |   0 | coffee 3   | 
+------+-----------+--------------------+ 
+0

2級很容易..這個例子是3,你可以看到。 您的查詢會拋出「'名稱'含糊不清',即使固定爲'c2.name' 該命令也不起作用 – 2013-02-15 08:24:57

+1

抱歉,'SELECT c1.name FROM category of c1 LEFT JOIN category as c2 ON c1.parent_id = c2.id OR(c1.parent_id = 0 AND c1.id = c2.id) ORDER BY c2.id,c1.id;'我只能看到2個級別(額外的乾酪參考比薩餅(1),而不是到奶酪(5)) – Solon 2013-02-15 08:32:29

+0

編輯'(7,5,'多餘的奶酪1.2.1');' – 2013-02-15 08:42:05

3

的一種方式是具有單獨的字符串字段,用於存儲任何節點的全路徑。 您需要在每個插入/更新/刪除操作中保留此字段。

你可以像下面

CREATE TABLE category(
    id INT(10), 
    parent_id INT(10), 
    name VARCHAR(50), 
    path VARCHAR(255) 
); 

INSERT INTO category (id, parent_id, name, path) VALUES 
(1, 0, 'pizza 1','|1|'), 
(2, 0, 'burger 2','|2|'), 
(3, 0, 'coffee 3','|3|'), 
(4, 1, 'piperoni 1.1','|1||4|'), 
(5, 1, 'cheese 1.2','|1||5|'), 
(6, 1, 'vegetariana 1.3','|1||6|'), 
(7, 5, 'extra cheese 1.2.1','|1||5||1|'); 

您需要通過路徑領域下令以正確的排序順序樹字段值。

SELECT * FROM `category` ORDER BY `path`; 

SqlFiddle Demo

這樣,你不需要編程語言遞歸打印整個樹正確的排序順序。

Note:

,如果你有最大的ID高達9,這個例子只會工作作爲| 1 || 11 |會早於| 1 || 2 |

要解決此問題,您需要爲基於ID字段的最大值建設字符串做填充有望爲您的應用程序,像例如與最大值預計低於999(3位)

|001||002|


根據我的經驗,這個解決方案應該只能處理深度達7-8級的樹。

對於其他方法:Click Here

+0

如果我添加額外的字段...並且必須維護它..它可能很簡單,就是一個'order'字段或類似的東西。對於文章來說,它很容易用遞歸代碼完成,我在這個基本結構上尋找SQL變體。 – 2013-02-15 12:43:06

12

嵌套樹與level列組合設置是閱讀和排序基於樹結構的一個非常好的方法。很容易選擇一個子樹,將結果限制到一定水平,並在一個查詢中進行排序。但是插入和刪除條目的成本相對較高,因此如果您在寫入數據時經常查詢數據,並且在讀取性能很重要的地方,則應該使用它。 (對於50-100的時間去除,插入或移動元素應該沒有問題,即使有1000個也不應該有問題)。

隨着你存儲它的level每個條目和值leftright,樣品在它下面的是:如果你想選擇只有1.2與它的後代,你會做(leftrightlevel):

SELECT * FROM table WHERE left >=7 AND right <=16 

,如果你想只選擇,如果你想進行排序,你可以做的孩子那麼

SELECT * FROM table WHERE left >=7 AND right <=16 AND level=2 
SELECT * FROM table WHERE left >=7 AND right <=16 ORDER BY left 

按照其他字段進行排序,同時保持層次結構的分組可能會有問題,具體取決於您想如何排序。

       1 (0,17,0) 
            | 
            | 
        +---------------+---------------------------------------+ 
        |              | 
       1.1 (1,6,1)           1.2 (7,16,1) 
        |              | 
     +------------+-------+     +-------------------+--------+----------------+ 
     |     |     |     |       | 
    1.1.1 (2,3,2)  1.1.2 (4,5,2)  1.2.1 (8,9,2)  1.2.2 (10,13,2)   1.2.2 (14,15,2) 
                    | 
                    | 
                    | 
                  1.2.2.1 (11,12,3) 

關閉表(完成,但我不建議您使用情況)。它將所有路徑存儲在樹中,因此如果有多個級別,層次結構所需的存儲空間將會非常快速地增長。

路徑枚舉有你存儲的每個元素的路徑與進入/0//0/1/查詢路徑很容易出現,但排序它不是靈活。

對於少量的entires,我會使用嵌套樹集。可悲的是我沒有一個很好的參考頁面來描述這些技術並對它們進行比較。

2

的Sql

WITH CTE_Category 
    AS 
    (
     SELECT id, parent_id, name 
     , RIGHT(name,CHARINDEX(' ',REVERSE(RTRIM(name)))-1) as ordername 
     FROM Category 
    ) 

    SELECT id, parent_id, name FROM CTE_Category ORDER BY ordername 

MySql的

SELECT id, parent_id, name 
FROM Category ORDER BY SUBSTRING_INDEX(name,' ',-1) 
+0

MySQL不支持CTE。看到http://stackoverflow.com/a/1382618/470838 – orangepips 2013-02-25 20:34:55

3

我認爲每個人都過建築師,荷蘭國際集團的解決方案。如果你的目標真的被你的例子所代表,就像虛擬頂級0 ID的3級,這應該就足夠了。

SELECT * 
    , id AS SORT_KEY 
    FROM category a 
WHERE parent_id = 0 
UNION ALL 
SELECT a.* 
    , CONCAT(b.id, '.', a.id) AS SORT_KEY 
    FROM category a 
    , category b 
WHERE b.parent_id = 0 
    and b.id = a.parent_id 
UNION ALL 
SELECT a.* 
    , CONCAT(c.id,'.', b.id,'.', a.id) AS SORT_KEY 
    FROM category a 
    , category b 
    , category c 
WHERE c.parent_id = 0 
    and b.id = a.parent_id 
    AND c.id = b.parent_id 
ORDER BY sort_key 
+0

有趣......但醜陋:)想象一下,如果你必須添加更多的列,連接和過濾器......在此之上。 – 2013-02-25 10:01:55

+1

您需要CTE,MySQL沒有。 – 2013-02-25 12:53:20