繁体   English   中英

命令sql树层次结构

[英]order sql tree hierarchy

对这样的表进行排序的最佳方法是什么:

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 ”末尾的数字仅用于可视化目的,不用于分类。 我把它们作为评论移动,太多人感到困惑,抱歉。

嵌套树集level列组合是一种非常好的技术,用于读取和排序基于树的结构。 可以轻松选择子树,将结果限制到特定级别,并在一个查询中进行排序。 但插入和删除entires的成本相对较高 ,因此如果您更频繁地查询数据然后编写它们并且读取性能很重要,则应该使用它。 (对于50-100的移除,插入或移动元素的时间应该没有问题,即使用1000也不应该有问题)。

随着你存储它的每个条目level的和值, leftright ,它下面的样本是:( leftrightlevel ,如果你想只选择) 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//0/1/查询路径很容易,但是对于排序它并不灵活。

对于少量的entires我会使用嵌套树集 遗憾的是,我没有一个很好的参考页面来描述这些技术并进行比较。

通过添加路径列和触发器,可以非常轻松地完成此操作。

首先添加一个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

小提琴

这样维护成本也很低。 插入时隐藏路径字段,并通过触发器计算。 删除节点没有开销,因为节点的所有子节点也被删除。 唯一的问题是更新节点的parent_id时; 好吧,不要那样做! :)

如果只有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           |
+------+-----------+--------------------+

一种方法是使用单独的字符串字段来存储任何节点的完整路径。 您需要在每个插入/更新/删除操作上维护此字段。

您可以拥有如下字段值

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演示

这样,您就不需要使用编程语言进行递归来以正确的排序顺序打印整个树。

Note:

此示例仅在最大ID为9时才有效,如| 1 || 11 | 比| 1 || 2 |更早出现

要解决此问题,您需要根据应用程序的最大ID字段值来构建字符串,例如下面的示例,最大值预期为999(3位数)

|001||002|


根据我的经验,这个解决方案应该只能处理深度达到7-8级的树。

对于其他方法: 单击此处

我认为每个人都过度设计解决方案。 如果您的目标实际上由您的示例表示,如在虚拟顶级为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

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)
SELECT * FROM category ORDER BY name, parent_id ASC

在SQL查询的末尾尝试ORDER BY name , id

这将按名称排序并使用id来解决任何关系。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM