对这样的表进行排序的最佳方法是什么:
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 或名称进行分层排序:
'pizza'//节点1
'piperoni'//节点1.1
'cheese'//节点1.2
'额外的奶酪'//节点1.2.1
'vegetariana'//节点1.3
'burger'//节点2
'coffee'//节点3
编辑: 名称末尾的数字是更好地可视化结构,而不是用于排序。
编辑2:如上所述...... name
“奶酪 1.2 ”末尾的数字仅用于可视化目的,不是用于分类。我把它们作为评论移动了,太多人感到困惑,抱歉。
答案 0 :(得分:12)
嵌套树集与level
列结合使用是一种非常好的技术,可用于读取和排序基于树的结构。可以轻松选择子树,将结果限制到特定级别,并在一个查询中进行排序。但插入和删除entires的成本相对高,因此如果您更频繁地查询数据然后编写它们并且读取性能很重要,则应该使用它。 (对于50-100的移除,插入或移动元素的时间应该没有问题,即使使用1000也不应该有问题。)
每个条目都存储level
left
和right
的值,下面的示例为:(left
,right
,{ {1}})如果您只想选择带有后代的level
,您可以这样做:
1.2
如果您只想选择孩子,那么
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/
查询路径很容易,但是对于排序它并不灵活。< / p>
对于少量的entires,我会使用嵌套树集。 遗憾的是,我没有一个很好的参考页面来描述这些技术并进行比较。
答案 1 :(得分:12)
通过添加路径列和触发器,可以非常轻松地完成此操作。
首先添加一个varchar列,其中包含从root到节点的路径:
ALTER TABLE category ADD path VARCHAR(50) NULL;
然后添加一个计算插入路径的触发器:
(简单地用父路径连接新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);
然后只需按路径选择顺序:
SELECT name, path FROM category ORDER BY path;
结果:
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时;好吧,不要那样做! :)
答案 2 :(得分:10)
如果只有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 |
+------+-----------+--------------------+
答案 3 :(得分: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`;
这样,您就不需要在编程语言中使用递归来以正确的排序顺序打印整个树。
Note:
此示例仅在最大ID为9时才有效,如| 1 || 11 |将比| 1 || 2 |
更早出现要解决此问题,您需要根据应用程序的ID字段的最大值来构建字符串,例如下面的示例,最大值预期为999(3位)
|001||002|
根据我的经验,这个解决方案应该只能处理深度达到7-8级的树。
对于其他方法:Click Here
答案 4 :(得分:3)
我认为每个人都过度设计解决方案。如果您的目标实际上由您的示例表示,如在虚拟顶级为0的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
答案 5 :(得分: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)
答案 6 :(得分:1)
SELECT * FROM category ORDER BY name, parent_id ASC
答案 7 :(得分:-1)
在SQL查询结束时尝试ORDER BY name , id
。
这将按名称排序并使用id来解决任何关系。