Mysql 顺序sql树层次结构
对这样的表进行排序的最佳方法是:Mysql 顺序sql树层次结构,mysql,sql,tree,Mysql,Sql,Tree,对这样的表进行排序的最佳方法是: 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
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
“素食者”//节点1.3
'burger'//节点2
'coffee'//节点3 编辑:名称末尾的数字是为了更好地显示结构,而不是为了排序
编辑2:如前几次所述。。。
名称
“cheese1.2”末尾的数字仅用于可视化,不用于排序。我将它们作为注释移动,太多人感到困惑,抱歉。请尝试在SQL查询结束时按名称、id排序
这将按名称排序,并使用id解决任何关系。如果只有3个嵌套级别,您可以执行类似操作
SELECT * FROM category ORDER BY name, parent_id ASC
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`;
请参见
这样,您就不需要在编程语言中使用递归来以正确的排序顺序打印整个树
注意:
此示例仅在max ID高达9时有效,因为| 1 | | | 11 |将早于| 1 | | 2|
要解决此问题,您需要根据应用程序预期的ID字段的最大值对构建字符串进行填充,如下面的示例所示,预期的最大值为999(3位)
| 001 | | 002 |
根据我的经验,该解决方案只适用于处理深度达7-8级的树
对于其他方法:嵌套树集与
级别
列相结合,是读取和排序基于树的结构的非常好的技术。在一个查询中选择一个子树、将结果限制在某个级别以及进行排序都很容易。但是插入和删除实体的成本相对较高,因此,如果查询数据的频率高于写入数据的频率,并且读取性能很重要,那么应该使用它。(对于50-100,移除、插入或移动元件的时间应该没有问题,即使是1000,也不应该有问题)
对于每个条目,您存储的是级别
和左
和右
的值,在下面的示例中,它是:(左
,右
,级别
)如果您只想选择1.2
及其子体,您可以执行以下操作:
SELECT * FROM table WHERE left >=7 AND right <=16
闭包表(用于完成,但我不推荐用于您的用例)。它将所有路径存储在树中,因此,如果有多个级别,层次结构所需的存储空间将增长得非常快
路径枚举在那里,您使用条目/0/
存储每个元素的路径,/0/1/
在那里查询路径很容易,但排序却没有那么灵活
对于少量的实体,我将使用嵌套树集。
遗憾的是,我没有一个好的参考页面来描述这些技术并对它们进行比较。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
我认为每个人都在过度设计解决方案。如果您的目标真的由您的示例表示,例如在虚拟顶层id为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
通过添加一个path列和一个触发器,这可以相当容易地完成 首先添加一个varchar列,该列将包含从根到节点的路径:
ALTER TABLE category ADD path VARCHAR(50) NULL;
然后添加一个触发器,用于计算插入时的路径:
(只需将新id与父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
看
这样维护成本也很低。路径字段在插入时隐藏,并通过触发器计算。删除节点没有开销,因为该节点的所有子节点也将被删除。唯一的问题是更新节点的父节点id时;好吧,别那么做!:) 是的。。但是名字就是一个例子。。为了更好地可视化结构,Oracle有一种方法可以使用
从父项开始\u id=0按先前的id连接=父项\u id按id排序同级ASC
。我认为MySQL没有这样的分层查询。@Benoit:实际上,除了少数数据库(包括MySQL)之外,几乎所有的数据库管理系统都可以使用递归公共表表达式执行类似的操作。tabel结构是否已经定义,或者您是否正在计划阶段,可以选择其他结构?您计划在表中有多少个条目?它是否经常被修改,或者对它有很多读访问权限是很重要的?@t.niese可以添加更多的列(top_parent_id,depth_level…),但如果可能的话,我正在寻找这种结构的解决方案。可以添加深度(树不限于3个级别),但通常不应超过50-100个条目,并且此特定查询的速度不是问题。请参阅以了解一些配方。2级别很简单。。如您所见,示例是3。您的查询抛出“'name'不明确”,即使已固定为c2.name
顺序也不正确,从类别中选择c1.name作为c1左连接类别作为c1.parent\u id=c2.id上的c2或(c1.parent\u id=0和c1.id=c2.id)按c2.id、c1.id排序代码>我只能看到两个级别(额外的奶酪指的是比萨饼(1),而不是奶酪(5)),这个函数是个不错的主意,尽管我会担心性能。好主意。@d.raev编辑过,它是有效的,但我给你的建议是将优先级保存在某个列中,你可以在添加新行时通过我的函数计算优先级。为什么函数会
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
ALTER TABLE category ADD path VARCHAR(50) NULL;
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