2013-02-15 67 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