从MySQL中的分层数据生成基于深度的树(无CTE)
嗨很多天,我一直在MySQL的这个问题,但我无法弄清楚。 你们有没有build议?
基本上,我有一个类别表,其中包括: id
, name
(类别名称)和parent
(类别的parent
id)。
示例数据:
1 Fruit 0 2 Apple 1 3 pear 1 4 FujiApple 2 5 AusApple 2 6 SydneyAPPLE 5 ....
有很多级别,可能超过3级。 我想创build一个sql查询,按照他的层次来分组数据:parent> child> grandchild>等
它应该输出树结构,如下所示:
1 Fruit 0 ^ 2 Apple 1 ^ 4 FujiApple 2 - 5 AusApple 2 ^ 6 SydneyApple 5 - 3 pear 1
我可以使用单个SQL查询来做到这一点吗? 我尝试过的另一种方法是:
SELECT * FROM category WHERE parent=0
在此之后,我再次遍历数据,并selectparent = id的行。 这似乎是一个不好的解决scheme。 因为它是mySQL,所以不能使用CTE。
如果您使用存储过程,您可以在从PHP到MySQL的单个调用中执行此操作:
示例调用
mysql> call category_hier(1); +--------+---------------+---------------+----------------------+-------+ | cat_id | category_name | parent_cat_id | parent_category_name | depth | +--------+---------------+---------------+----------------------+-------+ | 1 | Location | NULL | NULL | 0 | | 3 | USA | 1 | Location | 1 | | 4 | Illinois | 3 | USA | 2 | | 5 | Chicago | 3 | USA | 2 | +--------+---------------+---------------+----------------------+-------+ 4 rows in set (0.00 sec) $sql = sprintf("call category_hier(%d)", $id);
希望这可以帮助 :)
完整的脚本
testing表结构:
drop table if exists categories; create table categories ( cat_id smallint unsigned not null auto_increment primary key, name varchar(255) not null, parent_cat_id smallint unsigned null, key (parent_cat_id) ) engine = innodb;
testing数据:
insert into categories (name, parent_cat_id) values ('Location',null), ('USA',1), ('Illinois',2), ('Chicago',2), ('Color',null), ('Black',3), ('Red',3);
程序:
drop procedure if exists category_hier; delimiter # create procedure category_hier ( in p_cat_id smallint unsigned ) begin declare v_done tinyint unsigned default 0; declare v_depth smallint unsigned default 0; create temporary table hier( parent_cat_id smallint unsigned, cat_id smallint unsigned, depth smallint unsigned default 0 )engine = memory; insert into hier select parent_cat_id, cat_id, v_depth from categories where cat_id = p_cat_id; /* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */ create temporary table tmp engine=memory select * from hier; while not v_done do if exists( select 1 from categories p inner join hier on p.parent_cat_id = hier.cat_id and hier.depth = v_depth) then insert into hier select p.parent_cat_id, p.cat_id, v_depth + 1 from categories p inner join tmp on p.parent_cat_id = tmp.cat_id and tmp.depth = v_depth; set v_depth = v_depth + 1; truncate table tmp; insert into tmp select * from hier where depth = v_depth; else set v_done = 1; end if; end while; select p.cat_id, p.name as category_name, b.cat_id as parent_cat_id, b.name as parent_category_name, hier.depth from hier inner join categories p on hier.cat_id = p.cat_id left outer join categories b on hier.parent_cat_id = b.cat_id order by hier.depth, hier.cat_id; drop temporary table if exists hier; drop temporary table if exists tmp; end #
testing运行:
delimiter ; call category_hier(1); call category_hier(2);
使用雅虎geoplanet的一些性能testing会放置数据
drop table if exists geoplanet_places; create table geoplanet_places ( woe_id int unsigned not null, iso_code varchar(3) not null, name varchar(255) not null, lang varchar(8) not null, place_type varchar(32) not null, parent_woe_id int unsigned not null, primary key (woe_id), key (parent_woe_id) ) engine=innodb; mysql> select count(*) from geoplanet_places; +----------+ | count(*) | +----------+ | 5653967 | +----------+
所以在表中有560万行(位置)让我们看看从php调用的邻接列表实现/存储过程是如何处理的。
1 records fetched with max depth 0 in 0.001921 secs 250 records fetched with max depth 1 in 0.004883 secs 515 records fetched with max depth 1 in 0.006552 secs 822 records fetched with max depth 1 in 0.009568 secs 918 records fetched with max depth 1 in 0.009689 secs 1346 records fetched with max depth 1 in 0.040453 secs 5901 records fetched with max depth 2 in 0.219246 secs 6817 records fetched with max depth 1 in 0.152841 secs 8621 records fetched with max depth 3 in 0.096665 secs 18098 records fetched with max depth 3 in 0.580223 secs 238007 records fetched with max depth 4 in 2.003213 secs
总的来说,我对这些寒冷的运行时间非常满意,因为我甚至不会开始考虑将数以万计的数据行返回到我的前端,而宁愿dynamic构build树,每次调用只能获取多个级别。 哦,只要你认为innodb比myisam慢 – 我testing的myisam实现是所有计数的两倍。
更多的东西在这里: http : //pastie.org/1672733
希望这可以帮助 :)
在RDBMS中有两种常见的分层数据存储方式:邻接列表(您正在使用)和嵌套集合。 在pipe理MySQL中的分层数据方面,有很好的关于这些替代方法的文章。 您只能在嵌套集合模型的单个查询中执行您想要的操作。 但是,嵌套集模型使更多的工作来更新层次结构,因此您需要根据您的操作要求考虑权衡。
您无法使用单个查询来实现此目的。 在这种情况下,您的分层数据模型是无效的。 我build议你尝试两种在数据库中存储分层数据的方法:MPTT模型或“血统”模型。 使用这些模型中的任何一个都可以让您一次完成所需的select。
这里是一个文章与进一步的细节: http : //articles.sitepoint.com/article/hierarchical-data-database
线性方式:
我正在使用一个丑陋的函数在一个简单的string字段中创build一棵树。
/ topic title /001 message 1 /002 message 2 /002/001 reply to message 2 /002/001/001/ reply to reply /003 message 3 etc...
该表可用于通过简单的SQL查询来select树形结构中的所有行:
select * from morum_messages where m_topic=1234 order by m_linear asc
INSERT
只是select父线性(和子),并根据需要计算string。
select M_LINEAR FROM forum_messages WHERE m_topic = 1234 and M_LINEAR LIKE '{0}/___' ORDER BY M_LINEAR DESC limit 0,1 /* {0} - m_linear of the parent message*/
DELETE
很简单,就像删除邮件一样,或者通过线性删除父邮件的所有回复。